proposal-explicit-resource-management icon indicating copy to clipboard operation
proposal-explicit-resource-management copied to clipboard

Reintroduce bound `dispose`/`disposeAsync` getters

Open rbuckton opened this issue 1 year ago • 5 comments

As discussed in #231 and #229, this explores the possibility of reintroducing bound getters for DisposableStack.prototype.dispose/AsyncDisposableStack.prototype.disposeAsync. By making these methods getters, a DisposableStack/AsyncDisposableStack subclass that wishes to override disposal needs only to override the [Symbol.dispose]() or [Symbol.asyncDispose]() methods. By making them into bound method getters, an instance of either class can be more readily used as a field on an object literal:

Subclassing:

// current
class MyDisposableStack extends DisposableStack {
  [Symbol.dispose]() {
    // custom dispose logic
    super[Symbol.dispose]();
    // more custom dispose logic
  }

  static {
    // copy the new @@dispose to dispose
    this.prototype.dispose = this.prototype[Symbol.dispose];
  }
}

// proposed
class MyDisposableStack extends DisposableStack {
  [Symbol.dispose]() {
    // custom dispose logic
    super[Symbol.dispose]();
    // more custom dispose logic
  }
}

Object Literals:

// current
function makeBuffers() {
  using stack = new DisposableStack();
  const myResource = stack.use(new MyResource());

  doSomeInit(myResource);

  const moved = stack.move();
  return {
    myResource,
    [Symbol.dispose]() {
      moved.dispose();
    },
  };
}

// proposed
function makeBuffers() {
  using stack = new DisposableStack();
  const myResource = stack.use(new MyResource());

  doSomeInit(myResource);

  return {
    myResource,
    [Symbol.dispose]: stack.move().dispose,
  };
}

Fixes #229 Fixes #231

rbuckton avatar Jun 15 '24 21:06 rbuckton

A preview of this PR can be found at https://tc39.es/proposal-explicit-resource-management/pr/232.

github-actions[bot] avatar Jun 15 '24 21:06 github-actions[bot]

Ideally this can be pursued as a needs-consensus PR and not a follow-on to avoid making potentially breaking changes after implementations have shipped. I will bring this to the July/August 2024 TC39 plenary for discussion.

rbuckton avatar Jun 15 '24 21:06 rbuckton

In the (extensive) discussions we had around extending built-ins in 2022 (focused on, but not exclusively about, Set methods), we concluded that we weren't going to make future built-in methods defer to other built-in methods on this. Subclasses will need to override all relevant parts of the public interface.

I don't want to revisit that discussion, but to summarize, making this a bound getter rather than an alias will make non-subclass consumers slower, for the benefit of very slightly simpler subclassing. I think that's the wrong tradeoff and I believe the committee has agreed.

bakkot avatar Jun 15 '24 22:06 bakkot

Hm, fair point. What was the motivation for having the dispose / disposeAsync methods? What if we dropped them?

Besides the foot gun in https://github.com/tc39/proposal-explicit-resource-management/issues/231, it seems somewhat odd to me for the spec to define and give semantics for [Symbol.dispose] and [Symbol.asyncDispose], and then seeming not to prefer those methods in DisposableStack/AsyncDisposableStack, instead giving them string-named methods to call.

Should user space implementations of [Async]Disposable follow suit? Other symbol-named methods with semantics carved out by the spec generally don't do this.

rictic avatar Jun 22 '24 22:06 rictic

Should user space implementations of [Async]Disposable follow suit? Other symbol-named methods with semantics carved out by the spec generally don't do this.

They sometimes do: Array.prototype.values is an alias for Array.prototype[Symbol.iterator] (or rather, technically, the other way around); ditto for Map.prototype.values and Set.prototype.values.

String-named methods are nicer for consumers, but aren't a good basis for a protocol. So having both the symbol-named and string-named method is good practice, with one being an alias for the other.

As to the name, dispose is a good name for a method when the action is generic disposal, but sometimes it's going to be something like close or whatever, depending on the actual thing being implemented.

bakkot avatar Jun 22 '24 23:06 bakkot