Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using binding #15

Closed
jridgewell opened this issue Jul 24, 2018 · 56 comments · Fixed by #56
Closed

Using binding #15

jridgewell opened this issue Jul 24, 2018 · 56 comments · Fixed by #56

Comments

@jridgewell
Copy link
Member

jridgewell commented Jul 24, 2018

There's a lot of discussion on the IRC about making using a new binding type:

{

  using x = something();
  // use the resource

} // x[@@dispose]() gets called before block exits

I'll let the others fill in their thoughts.

@decompil3d
Copy link
Member

decompil3d commented Jul 24, 2018

Filling in some more from backchannel IRC during the presentation:

  • using binding should be constant like const -- otherwise, it gets ugly when you reassign mid-scope:
{
  using x = something();
  x = somethingElse();
}

// is the result of `something()` disposed? what about the result of `somethingElse()`? both?
  • @domenic suggested the idea of using const x = something();
    • if we choose to support non-constant, this is a reasonable mechanism to allow both let and const semantics (i.e. using let x = something(); using const y = somethingElse();)
  • This matches C++ stack variables nicely, so easy pattern to explain
  • Avoids some of the weirdness around how exceptions are handled when using the try(stuff) syntax
  • Avoids cover grammar issue

@ljharb
Copy link
Member

ljharb commented Jul 24, 2018

or const using x?

@jridgewell
Copy link
Member Author

If we want to allow mutability, we could instead associate using with the value instead of the identifier:

const x = using something();
let y = using something();
y = somethingElse();

@decompil3d
Copy link
Member

@ljharb I'm not 100% opposed to that, but I do like the natural readability of using const x = I am using the const -ant variable x

@domenic
Copy link
Member

domenic commented Jul 24, 2018

So, the issue here is that for the using (acquireResource()) { ... } case, you end up needing to create an otherwise-useless binding:

using (acquireResource()) {
  doStuff();
}

vs.

{
  using const justWaitingForThisToGoOutOfScope = acquireResource();
  doStuff();
}

This is a pattern you see a lot in C++ codebases (e.g. HandleScope scope; and then nothing uses the scope variable), and is a bit unfortunate.

I realize this post would be more compelling with an actual example of something where you don't need to use it inside the block. I've seen many such cases, but can't recall one off the top of my head. @rbuckton may have some ready.

@rbuckton
Copy link
Collaborator

A using const x = syntax raises the following concerns for me:

  • introduces an implicit finally at the end of the containing block, which might throw an exception from calling @@dispose. In a lengthy block this statement could be anywhere, so it's harder to associate cause/effect vs. A using (...) {} block that makes this explicit.
  • There are valid scenarios for a non-declaration form. using (x); is already valid ECMAScript.
  • using const is still an ASI hazard unless you add a NLT restriction.
  • To handle scoping concerns like the proposal does, you have only inverted the syntax, e.g. { using const x = ...; { using const y = ...;} }

@Jamesernator
Copy link

@domenic A good example would be the classic lock one:

using (await lock.acquire()) {
  const state = await getState()
  const newState = await changeState(state) 
  await writeState(newState)
}

@devsnek
Copy link
Member

devsnek commented Jul 25, 2018

could just allow:

{
  using lock();
  using const x = ...;
  ...
}

but its a bit awkward since variable declarations don't return anything

@littledan
Copy link
Member

using const is still an ASI hazard unless you add a NLT restriction.

We would definitely need an NLT after the using. I'm not sure this issue is very bad. It seems similar to the one introduced by let in sloppy mode. I don't think we need cover grammars to resolve it. Somehow, the issue with starting the opening bracket on the next line seems worse.

@rbuckton
Copy link
Collaborator

rbuckton commented Aug 3, 2018

I'm also worried this introduces even more confusing semantics:

{
  using const a = ;
  // some code
  using const b = ;
}

Both a and b are const, and therefore have TDZ. If an exception occurs at // some code above, b hasn't been initialized, so we have to check not only for null or undefined, but also whether b was initialized.

Conversely:

using (const a = ) {
  // some code
  using (const b = ) {
  }
}

This syntax is much clearer and more explicit about scoping and lifetime, and aligns with how we initialize bindings in statements like for, for..in, and for..of.

@decompil3d
Copy link
Member

Those are good points @rbuckton. I like the C#-style using as well. But given the grammar issue, it seems like the Java-style try-with-resources is the right compromise.

@rbuckton
Copy link
Collaborator

Please note that this proposal has moved to using the try-with-resources syntax for this feature to resolve the grammatical complexity of a cover grammar for using.

@friendlyanon
Copy link

Would like to mention that C# 8 has introduced using declarations and Java has Project Lombok's @Cleanup annotation.
Would JS developers have to resort to code transforming build tools to achieve flat code without unnecessary indentations?
Would this kind of usecase be covered in a later addition to this feature?

@rbuckton
Copy link
Collaborator

If we could have used using without the cover grammar, I wouldn't have been opposed to having using (const x = ...) {}, using (x) {}, and using const x = .... The meaning seems more ambiguous with try however:

{
  try const x = ...;
}

As mentioned earlier, only having using let/const x = would result in cases where developers would have to introduce an unnecessary binding. The using declaration form in C# 8 is merely a more limited syntactic sugar over the using statement form.

Does anyone think try const x would be a non-controversial addition to this proposal?

@friendlyanon
Copy link

As an additional bit of information: C++'s P0709 R2 proposal would use the prefix try for a different purpose. Would that be a concern? Maybe this being only a proposal, it can safely be disregarded as having no real potential of being confusing.

@rbuckton
Copy link
Collaborator

Project Lombok's @Cleanup annotation seems to either ignore or predate Java's try-with-resources statement, especially in their "Vanilla Java" example on the linked page.

@rbuckton
Copy link
Collaborator

Another option would be to split the difference:

// try-using statement for existing value
try using (expr) {
}
// try-using statement for new declarations
try using (const x = ...) {
}
// block-scoped using declaration
{
  using const x = ...;
}

@devsnek
Copy link
Member

devsnek commented Jul 15, 2019

if using Declaration exists there's not much of a reason for the other ones to exist because they all have blocks too

@rbuckton
Copy link
Collaborator

I disagree, Domenic's example in #15 (comment) illustrates this. We should have a form that does not require a binding, and using (expr) conflicts with call expression.

@devsnek
Copy link
Member

devsnek commented Jul 15, 2019

@rbuckton makes sense 👍

@ljharb
Copy link
Member

ljharb commented Jul 15, 2019

what if “try using” didn’t create any bindings, but just took them? like:

try using (x, y, z) {
  const x = 1;
  let y = 2;
  var z = 3; // maybe?
  const a = 4; // not disposed
}

and it’d only allow identifiers that were declarations inside the try block? (or allow none, ofc)

@rbuckton
Copy link
Collaborator

That has the same problem as using const x = , you're forcing developers to introduce bindings in situations where they would otherwise be unnecessary. If I had to choose between try-with-resources (allowing both declarations and expressions), or using const x (that only allows declarations), I'd chose try-with-resources in a heartbeat.

@friendlyanon
Copy link

@ljharb the goal here I believe would be to have an alternative syntax for try-with-resources that would not introduce an additional scope at the source code level, much like the features in other languages I pointed out here.

@russelldavis
Copy link

russelldavis commented Jul 25, 2019

What if, instead of a new using keyword, we rely on importing a using function from a module in the stdlib (lets say std:using), and the js engine would give it special treatment (a bit gross, but I wonder if the upsides outweigh it). (EDIT: we could also use a separate prefix like lang: for special imports like this to help distinguish them.)

It would require no grammar/syntax changes, avoids compatibility issues, avoids unnecessary bindings, and lets you avoid adding unnecessary additional scopes.

Example:

import {using} from 'std:using';
function foo() {
  using(lock.acquire());
  const file = using(openFile("foo.txt"));
  doStuff(file);
}

This approach also opens up another possibility: the concept of scope disposer functions that can be passed around. A downside of the Dispoable approach in most languages is that it's left to the programmer to remember to call using, and requires the programmer to read docs or inspect the code to even know that a function expects its return value to be disposed.

What if, when calling a function like openFile, you were required to deal with its disposal? Libraries could provide a stricter interface with functions that require a disposer parameter. The stdlib could provide a defer function that (with help from the js engine) automatically generates a disposer for the current scope. For example:

import {defer} from 'std:defer';
const at_exit = process.on.bind(process, 'exit');
const no_dispose = () => {};

function openFile(filename, disposer) {
  const file = _privateOpenFile(filename);
  disposer(() => closeFile(file));
  return file;
}

function foo() {
  const file1 = openFile("foo.txt", defer); // closes at the end of the current scope
  const file2 = openFile("foo.txt", at_exit); // closes when the program exits
  const file3 = openFile("foo.txt", no_dispose); // never auto closed
}

And of course, defer could be used on its own:

import {defer} from 'std:defer';
function foo() {
  console.log("Entering foo");
  defer(() => console.log("Exiting foo"));
  doStuffThatMightThrow();

This will look very familiar to folks who know Go. But it's way more powerful, since you can pass defer around as a function (as shown earlier). It's how I always wish Go would've implemented it.

For any scope that includes defer or use, the js engine would insert an implicit finally block with the appropriate logic. This would be easy to polyfill with babel.

Thoughts on this approach?

@Veetaha
Copy link
Contributor

Veetaha commented Sep 29, 2019

Another option would be to split the difference:

// try-using statement for existing value
try using (expr) {
}
// try-using statement for new declarations
try using (const x = ...) {
}
// block-scoped using declaration
{
  using const x = ...;
}

@rbuckton
This seems like an adequate decision as for me and maybe we could shorten using keyword to just use while we can? And yes, the third syntax should definitely be included in the proposal alongside first two ones!

It is tedious to create the new scope only for automatic disposal.
When you have a bunch of handles to create in one scope it is much cleaner:

function blah() {
    use const foo = new FooHandle();
    use const bar = new BarHandle();
    use const baz = new BazHandle();
    // ... Almost no changes to standard code layout. C++ RAII concept in its beauty!

    // Automatic cleanup
}

instead of

function blah() {
    try use (
          const foo = new FooHandle(),
          bar = new BarHandle(),
          baz = new BazHandle()
    ) {
        // ... Too much indentation, why do I need this new scope?

        // Automatic cleanup
    }
}

And what are your thoughts on switching to use instead of using?

@ljharb
Copy link
Member

ljharb commented Sep 29, 2019

“use” is a verb here; “try using” works together in English but “try use” doesn’t.

@Veetaha
Copy link
Contributor

Veetaha commented Oct 4, 2019

@rbuckton @ljharb Ok, you may have a point. I created a related lightweight babel macro for using bindings. It has much like @russelldavis proposal syntax, since it looks like just a function call, but doesn't add AggregateError overhead (destructors should be noexcept from e.g. C++ point of view the program is ill-formed otherwise). Could it be added to references section of this proposal?

https://github.com/Veetaha/using.macro

@rbuckton
Copy link
Collaborator

rbuckton commented Nov 9, 2019

I've been considering this a bit more, including how to leverage this for values as well:

{
using const x = expr; // dispose 'x' at end of block (EOB) 
using const { a, b } = expr; // dispose 'a' & 'b' at EOB
using value expr; // dispose value of 'expr' at EOB

... 
}

At declaration:

  • In Async Function, capture decl value and @@asyncDispose property value if present, otherwise:
  • Capture decl value and @@dispose property value if present; otherwise, throw a TypeError

At EOB/exception:

  • Call each captured dispose with the decl value as the 'this'.

Pros:

  • avoids confusion over whether the decl is visible to the catch/finally of a try-using-catch/finally
  • using const avoids ambiguous parse for destructuring (i.e. using [x] = y would be ambiguous)
  • using value avoids ambiguous parse when the binding is unneeded (i.e. using (y) would be ambiguous)

Also, I've been considering introducing a Disposable/AsyncDisposable global that could be used as a container for aggregation of disposable resources (i.e. Disposable.from(x, y, z)) and as a wrapper for a callback (i.e. new Disposable(() => whatever)). This could be used in concert with using value to emulate Go's defer:

{
  const file = openFile();
  using value new Disposable(() => file.close());
 ... 
}

More verbose, obviously, but fairly flexible.

I'm not sure whether it would be necessary to introduce an async using const ... or async using value .... I think @erights would need to weigh in on that.

@rbuckton
Copy link
Collaborator

If we like using const and using value, would we prefer that over try using () and should I propose this change at the next TC39 meeting? Specifically, switching to using const/using value and dropping the try using (...) {} form.

Also, I've gone back and forth about how I feel about how destructuring should be handled in these cases. Should we consider restricting using const to binding identifiers only and disallow binding patterns to reduce confusion here? We could then revisit whether to support binding patterns in a later proposal.

CC: @leobalter, @dtribble (who have also expressed interest in the syntax).

@rbuckton
Copy link
Collaborator

@littledan ambiguity with ParenthesizedExpression: using (expr) is already parsed as a call.

@leobalter
Copy link
Member

At first, I like the idea of this:

{
  using const x = ...
 
} // dispose values at the end of block 

but this leads me to the question where disposing is also very useful on abrupts... so I wonder how we dispose resources.

If that means:

let called = 0;
{
  using const x = { [Symbol.dispose]() { called += 1; };
}
assert.sameValue(called, 1);

what goes in here?

let called = 0;
try {
  using const x = { [Symbol.dispose]() { called += 1; };
  throw 'foo';
} catch {
  assert.sameValue(called, 1); // Is this correct?
}

The moment values are disposed are really the key for me to understand the whole thing here.

I like the content from #15 (comment), btw.

@decompil3d
Copy link
Member

@leobalter I think that the disposal would happen after the catch block, as it is similar to using a finally for disposal.

@devsnek
Copy link
Member

devsnek commented Nov 11, 2019

I'd expect it to happen at the end of the try block, when it leaves scope.

@rbuckton
Copy link
Collaborator

As @devsnek says, the disposal would happen at the end of the try block. If an exception were to occur during disposal, you would see that exception in the catch block.

@rbuckton
Copy link
Collaborator

rbuckton commented Nov 11, 2019

To clarify, @leobalter's example:

let called = 0;
try {
  using const x = { [Symbol.dispose]() { called += 1; };
  throw 'foo';
} catch {
  assert.sameValue(called, 1); // Is this correct?
}

Would be essentially the same as this:

let called = 0;
try {
  const x = { [Symbol.dispose]() { called += 1; };
  const $tmp_has_x = x !== null && x !== undefined;
  const $tmp_x_dispose = $tmp_has_x ? x[Symbol.dispose] : undefined;
  if ($tmp_has_x && typeof $tmp_x_dispose !== "function") throw new TypeError();
  try {
    throw 'foo';
  }
  finally {
    if ($tmp_has_x) $tmp_x_dispose.call(x);
  }
} catch {
  assert.sameValue(called, 1); // Is this correct? yes, dispose has been called
}

@decompil3d
Copy link
Member

I stand corrected :)

@leobalter
Copy link
Member

Thanks for the clarification, @rbuckton. I don't have any objections, it LGTM.

I like it does offer consistency with any other usage within blocks. And a try catch should be manageable from within:

{
  using const x = { [Symbol.dispose]() { this.close(); }, ... };
  try {
    connect();
    // For common usage, we are not trying to catch the parts from `using`,
    // which seems like just a setup
  } catch (e) {
    // capture errors from connect() (or whatever code)
  }
} // disposed here

Although, I believe there might be concerns from other people where we need the dispose happening after a catch. I'm fine one way or another, we're in a good path syntax wise.

@erights
Copy link

erights commented Nov 12, 2019

I offer a variant for discussion. I don't know what I think of it either, so I am neither arguing for or against it at this time.

In the same way that await expr and yield expr are expressions, let's say that using expr is an expression. Then using const x = expr; would instead just be

const x = using expr;

The using value syntax would be trivially absorbed. Since disposal is postponed to block exit anyway, in reverse order of initialization, there doesn't seem to be any requirement that these be introduced in statements or declarations rather than expressions. We'd need to be careful with the semantics of a using expr inside a using expr, but this should have the same equivalences as nested awaits or yields, i.e.,

{
   using f(using g());
  // ...
}

should be equivalent to

{
   const tmp = using g();
   using f(tmp);
   // ...
}

@devsnek
Copy link
Member

devsnek commented Nov 12, 2019

That's pretty interesting. However, I'd argue that it's also more confusing. I like that you have to explicitly write out

{
  const tmp = using value g();
  using value f(tmp);
}

because it reminds you exactly what the scope and "lifetime" of the value are.

@rbuckton
Copy link
Collaborator

Please keep in mind that using cannot be used in an expression position without introducing a possible parse ambiguity:

const temp = using g(); // seems ok...
const temp = using (g()) // calls a function named "using"
const temp = using [g()] // element access on an object named "using"

using f(tmp); // seems ok...
using (f(tmp)); // calls a function named "using"
using [f(tmp)]; // element access on an object named "using"

You might consider some kind of restriction to prevent ParenthesizedExpression, but there are valid use cases to allow parens:

using (void 0, container).func(); // Oops... I wanted to call `container.func` 
                                  // without a `this` and dispose of its result
using (a ?? b) || c;

That's why I proposed using value Expression for the expression case as there would be no parse ambiguity:

using value f(tmp); // disposes the result of f(tmp) at end of block
using value (f(tmp)); // disposes the result of f(tmp) at end of block
using value (void 0, container).func(); // does what I expect.

I'm not certain I'm in favor of using value ... being an expression, as it seems like it could cause some confusion with scoping. using const ... and using value ... introduce block-scoped disposal, so I could see this case being confusing to users:

{
  f(() => using value g()); // no {} to indicate a block.
}

Versus having it be a statement:

{
  f(() => {
    using value g(); // definitely scoped to the block for the arrow.
  });
}

@erights
Copy link

erights commented Nov 12, 2019

@rbuckton Good point! I hadn't considered expressions in arrow functions without curlies. I agree that this would be too confusing. Another place this comes up: field initialization expressions. Where else do we have effective block boundaries without curlies?

@ljharb
Copy link
Member

ljharb commented Nov 12, 2019

If i understand your question correctly, if/else/do/while/for braceless blocks? (not sure if you’re only looking for places a statement is allowed and a block is implied)

@rbuckton
Copy link
Collaborator

if/else, do-while, and while do not introduce an implicit block scope. for, for-in, and for-of do, primarily for the purpose of generating per-iteration bindings.

@erights
Copy link

erights commented Nov 12, 2019

Hi @ljharb , good point! I was not thinking of those cases, but rather, cases like arrow functions and initialization expressions where there's an implied block, but only around an expression, not a statement or declaration.

If this proposal were for a statement, then the ability to omit curlies in if/else/do/while/for indeed be a similar problem. But since @rbuckton is proposing only declarations, not statements or expressions, his proposal would force curlies in this context as well. This is a big +1 for making it a declaration specifically.

Looking through the grammar, I found the following interesting places a declaration could occur:

for (const using x = ...; ...; ...) {}
switch (...) { case ...: const using y = ...; ... }
export const using z = ...;
// top level of module using top-level await

Is the behavior for all of these clear? The one that seems most worrisome is the switch.

@rbuckton
Copy link
Collaborator

rbuckton commented Nov 12, 2019

@erights: A few notes:

  • for (const using x = ... for (using const x = … has an implicit block scope due to per-iteration bindings.
  • switch (...) { ... } has an explicit block scope due to the case block { }. This is already evident as the following is an early error:
    switch (x) {
      case 0: const x = 1; break;
      case 1: const x = 2; break; // error due to duplicate declaration of `x`
    }
  • export const using z = ...; export using const z = …; seems like something we would want to forbid, since the z would be disposed when the source file finishes execution (as a Module is itself an implicit block scope).

Also, the proposed syntax is using const rather than const using, so it would be easy to disallow export using const.

@erights
Copy link

erights commented Nov 12, 2019

All seem reasonable. Thanks!

@dead-claudia
Copy link

I didn't catch this initially, but this reminds me a ton of an idea I came up with a couple weeks prior to this issue being created.

@theScottyJam
Copy link

theScottyJam commented Jul 22, 2021

question: what happens here?

// Example 1
{
  using const x = { [Symbol.dispose]() {} }
  delete x[Symbol.dispose]
} // Does an error get thrown?

// Example 2
{
  using const x = { [Symbol.dispose]() {} }
  x[Symbol.dispose] = () => console.log('Hi there')
} // Does "Hi there" get logged?

Based on the transpiled examples, I understand that in example 1 an error will not be thrown, and in example 2 "Hi there" will not be logged. What this tells me is that the "using const" causes a single event to happen when a declaration happens (that event is registering an exit handler on the scope), and that the resulting declared variable hasn't been made special or different in any way from a normal const. The only relationship using has to the declared variable is that the disposal action will trigger when the scope exists, which happens to be the same time the resulting variable leaves scope, but these two things aren't entangled in any way, as can be shown when Symbol.dispose gets swapped around. My initial expectation was that the declaration would have been made special, and that the checks for Symbol.dispose wouldn't happen until the scope exists, because that's what the syntax suggests.

I vote we follow @rbuckton's idea and drop using const and turn using value into an expression.

We have options on how to handle the () => using value x case - as long as we can find a consistent and predictable way to handle it, it shouldn't trip users up.

  • Option 1: Require using value to only be used in a place where an explicit scope has been drawn out with {} (It can also be used at the module-level). The disposal will happen once the explicit scope markers have been reached. Even if we're inside an implied scope, such as one created by a for loop without {}, disposal won't happen until the visible {} bound are reached.
    if (condition) { using value x } // ok
    function() { using value x } // ok
    const f = () => using value x // Syntax error
    {
      for (const x of array) using value x // ok - gets disposed on outer scope. This is pretty useful syntax too.
    }
  • Option 2: Require users to place an explicit marker on the scope that the using value applies to. Top-level using value is allowed, just like top-level await is allowed.
    cleansUp { using value x } // ok
    if (condition) cleansUp { using value x } // ok
    function() cleansUp { using value x } // A little awkward, but ok
    const f = () => using value x // Syntax error
    cleansUp {
      for (const x of array) using value x // ok - gets disposed on outer scope
    }
    cleansUp {
      for (const x of array) {
        using value x // ok - disposes on the outer scope
      }
    }

I personally prefer the first option. I think it's a very simple ruleset to remember - you can only use using value in places with {}. I also feel like this rule set isn't any more confusing than the current ruleset for the statement version of using value - which is that you can only use the statement version where a declaration is allowed, so for (...) using value x is illegal, even though it looks like it should be perfectly legal.

Some other benefits of using value as an expression:

  • We're not forcing people who don't like to use const to use const. I know coffeescript doesn't even provide a const option to its users. (I love const, but not everyone does).
  • We're forcing people to assign things to temporary variables, because they're not able to use using value in the middle of an expression:
      // This is not currently possible, but there's no reason why it shouldn't be.
      // You can imagine how messy the current solution for this would be.
      const openFiles = {
        fileName1: using value openFile('./fileName1.txt'),
        fileName2: using value openFile('./fileName2.txt'),
        fileName3: using value openFile('./fileName3.txt'),
        // ...
      }
  • We cut down on the amount of syntax we're introducing. Instead of three new constructs for resource management (try using, using const, and using value) we just have two. And if we just drop try using (which I'm in favor of), we'll be left with a single new operator - using value.

@rbuckton
Copy link
Collaborator

rbuckton commented Oct 6, 2021

@theScottyJam

I vote we follow @rbuckton's idea and drop using const and turn using value into an expression.

I'm not sure where you got that impression. I specifically said this, above:

I'm not certain I'm in favor of using value ... being an expression

We've also discussed this in plenary and the consensus was that allowing using value in an arbitrary expression position is not a direction we want to go and would not advance. The using statements introduce an "action at a distance" effect at the end of the enclosing block, and we'd like to minimize the possibility that this effect becomes hidden to the user. Requiring this to be a statement assuages enough concerns do to the increased visibility of a statement vs. an expression.

@rbuckton
Copy link
Collaborator

rbuckton commented Oct 6, 2021

Note: I've updated #56 to disallow export using const.

@theScottyJam
Copy link

I'm not actually sure where I got that from - perhaps I just misinterpreted what you said.

My vote would still be for using value being an expression, though I do understand the concerns you brought up about it.

At the very least, perhaps it would be nice to actually make the declarations special, like the syntax suggests it's happening. i.e. the engine doesn't look for the Symbol.dispose property on a "using const" declaration until the block is exited, at which point it'll look up a disposal handler and call it. I know in practice the Symbol.dispose property should never be changed, but in principle, I think this behavior helps make the mental model more intuitive, at least it does for me, perhaps others would disagree.

@dead-claudia
Copy link

@theScottyJam I thought that was the original intent of using a declaration-based syntax instead of a Java/C#-like using (value) { ... } block?

@theScottyJam
Copy link

From what I understand, when you use using const, it'll immediately look for a Symbol.dispose property and remember the callback stored there. It doesn't wait until the end of the block.

Is this what you're asking? Or could you clarify?

@rbuckton
Copy link
Collaborator

rbuckton commented Oct 7, 2021

@theScottyJam we have already discussed in plenary when [Symbol.dispose] should be read, and the decision was that it needs to happen at the declaration site and not at the end of the enclosing block. These semantics were was necessary to achieve Stage 2 consensus (see #34) and are unlikely to change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.