tc39 / proposal-explicit-resource-management

ECMAScript Explicit Resource Management
https://arai-a.github.io/ecma262-compare/?pr=3000
BSD 3-Clause "New" or "Revised" License
725 stars 28 forks source link

"Explicitly Managed Resource" as an object field #229

Closed dtkdtk closed 1 month ago

dtkdtk commented 1 month ago

Is this use case thought out? Is there a syntax like { using propKey: propValue } or { propKey: using propValue } ? As far as I understand, the only way to put a disposable resource into an object is to declare a using variable outside the object, and then create an object field with that variable:

using myResource = new MyResource();
const resources = {
    myResource
};

However, I think that for this case too, syntax should be provided. For example:

const buffers = {
    myResource: using new myResource();
};

Also: If an object is deleted, are the "Explicitly Managed Resources" fields deleted correctly?

mhofman commented 1 month ago

What semantics are you looking for exactly?

the only way to put a disposable resource into an object is to declare a using variable outside the object, and then create an object field with that variable

That will likely not do what you expect. When the scope reaches the end, your resource will be disposed, and your object will simply hold onto a disposed resource. You likely want to "move" the disposal responsibility onto the owner object.

Also: If an object is deleted, are the "Explicitly Managed Resources" fields deleted correctly?

There is no such thing a "an object is deleted" in JavaScript. You probably mean "an object gets garbage collected". This proposal is about explicit resource management, whereas garbage collection is not explicit. A collected object does not (and cannot) trigger any explicit disposal logic. A good summary of such discussions can be found at https://github.com/tc39/proposal-explicit-resource-management/issues/213#issuecomment-1909158242

Taking the explicit nature of the proposal into consideration, semantics for using field that could make sense would be to automatically make the object a disposable itself. To rewrite your example above:

function makeBuffers() {
  return {
      using myResource: new myResource();
  }; // object literal implicitly has a `[Symbol.dispose]` property
}

using buffers = makeBuffers();

However beyond this simple use case, you'll quickly notice that you may want to do some work between when you obtain your resource in the maker, and when you return the object. At that point, you should be using a disposable stack to manage your resources:

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

  doSomeInit(myResource);

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

using buffers = makeBuffers();

Admittedly "moving" the disposable stack to the object's dispose is a little clunky, but I'm not sure there is a much better approach here. Also since you don't want to expose the internal disposer, I don't see how any "per field" syntax could help here. What we may need is a userland helper that moves the stack onto the object:

function moveToDisposeFunction(stack) {
  const disposer = stack.move();
  return disposer.dispose.bind(disposer);
}

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

  doSomeInit(myResource);

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

using buffers = makeBuffers();
dtkdtk commented 1 month ago

Thanks for your answer. It completely covers the issue.

Also: If an object is deleted, are the "Explicitly Managed Resources" fields deleted correctly?

Let me rephrase: When a non-explicitly managed object containing explicitly managed resources goes out of scope, are the @@dispose methods of those resources called? However, you already answered that question: "Yes". After researching more examples (including yours), I found a better solution. Although using DisposableStack makes my task a bit more complicated, it may be better than creating a new syntax to automate actions with a disposable stack.

I don't see a problem here anymore.

rbuckton commented 1 month ago

What we may need is a userland helper that moves the stack onto the object:

I originally had that capability with DisposableStack.prototype.dispose being a getter for a bound method, but that approach was rejected by some on the committee.

mhofman commented 1 month ago

I originally had that capability with DisposableStack.prototype.dispose being a getter for a bound method, but that approach was rejected by some on the committee.

Yeah I thought I remembered something along those lines. Wondering if it may be worth revisiting.