377 comments

[ 0.24 ms ] story [ 771 ms ] thread
This would make my webassembly memory management much cleaner and ergonomic. Same goes for WebGPU, probably.
Useful in C# and should be just as useful in TS. Nice.
As long as we don't have to do the dipose dance (isDisposed/isDispoing booleans).
This awkward dance is due to destructors/finalizers calling `Dispose()`. Since JS doesn't have those, this shouldn't be an issue.
It's been a while since I coded in C# but you just triggered some bad memories.
Nice, so this is the equivalent of `with` and context mangers in Python. I like it.

I generally avoid new TS features (apart from typing that gets compiled away) until it looks like they are going to make their way into JavaScript, anyone know if thats being considered?

--

Edit: Yes it's being considered, looks likely, but not decided - https://github.com/tc39/proposal-explicit-resource-managemen...

The article mentions that this proposal is in stage 3 for Javascript, so will probably be refined a bit more https://tc39.es/process-document/
Things rarely change at all once they're at stage 3. That's why TS waits until that stage before implementing. There might be a minor tweak in some edge cases but stage 3 is usually "done".
> Nice, so this is the equivalent of `with` and context mangers in Python.

Well, half of it at least :)

Python has both enter and exit, while this proposal only has the exit half.

TBF the `__enter__` hook is not the most useful.
More importantly, it does not allow distinguishing between successful completion and a thrown exception. Which may or may not be such a great feature in the first place, but still.
TypeScript only implements JS features that make it to Stage 3, the only exception being decorators in the past which never went beyond Stage 2 in their previous form.
Those first draft decorators never made it past Stage 1. That's also why Typescript required a compile-time flag that began with `--experimental` before you could use them. It's amazing how many projects put an `--experimental` flag into Production code. (Thanks, Angular.)
Which is funny since Javascript had `with` before (that did something completely different) that was overly abused and thus removed.
This is a JavaScript keyword and there's nothing TypeScript-specific in the blog post.
typescript is introducing it in a polyfill, which is going to be how a lot of people will first get to use the feature
(comment deleted)
How does the polyfill work? How does TS know the object can be disposed?
You're telling it through the `using` statement?
I mean the implementation of the polyfill, in JS
That has the same answer. I'm not sure what you're looking for.

The polyfill adds code at the start of the block to make an array, and code at the end of the block to call dispose on everything in the array. Then the transpiler just has to turn "using" into an append.

There's some other details to handle exceptions but that's the basics of how it works.

Edited: The code, polyfill or native, doesn't really know if things can be disposed. It just tries to do it, because you told it to. If the variable is null it skips it, otherwise it asserts that the dispose function exists at using time, and blindly runs it at the end of block.

Thanks, that's exactly what I was wondering.

What happens if you save a reference to (or return, etc.) the variable you declared with "using"? Then it will end up automatically disposed, but from the programmers point of view it should still be usable (not disposed).

> from the programmers point of view it should still be usable (not disposed)

Then that programmer had their hopes too high. The basic "using" keyword is not for variables you want to move between scopes. Javascript doesn't do reference counting.

For saving/returning, you need to use explicit instances of DisposableStack and .move()

I get you, but it's both a TypeScript and JavaScript feature, and more importantly TypeScript is including it in its next official release whereas it is still not part of ECMAScript (though close, at stage 3).

So speaking specifically of a new TypeScript feature is justified here.

The article doesn't hide that, and there is something TS-specific: the information that it (that JavaScript version) will come in v5.2.

I think it's fair enough if you're coming from a TS perspective, writing TS (specifically) or blogging about it. Obviously there's overlap.

Quite a useful change at first glance, equivalent to go’s `defer`?

If you use react, it can get a lot more interesting[0]

[0] https://twitter.com/JLarky/status/1669741878324113408

It's not really the same as defer. If you're working with a file, you have to "open" and "defer close" separately. The point of "using" is to combine those into one statement -- it's more like RAII.
True, “equivalent” was the wrong word to use.
It's not really like RAII either, because you have to `using` the object.

It's a closer cousin of Python's context manager (aka C#'s using statement — probably where it actually comes from — and Java's try-with-resource), which probably hew closer to Common Lisp's unwind-protect (that is not type-oriented, but usually you'd create your own macro around that).

You’re right, but I don’t see a big difference in practice if the compiler is able to check that you’re using “using” correctly! Hopefully there’s at least a warning if you call something that returns a disposable object, but don’t dispose of it.
It's neat, but if I understand it correctly, if you forget to write the "using" keyword the code will still work just fine but the dispose will never be called, resulting in a memory leak. I don't suppose there is a way to mark a function as being un-callable unless you do so with "using"?
well maybe you want to explicitly free it later...
(comment deleted)
JavaScript is a garbage collected language, so no memory leak is possible.
Garbage collection doesn't prevent memory leaks. Nothing stops you from filling up a Map and never emptying it
It's not a leak if the memory is still referenced! A garbage-collected system can never leak memory.
You’re being pedantic. “Memory leak” is colloquially used when you have data in memory which accumulates and isn’t actively being used by the system.
(comment deleted)
The problem with that definition of "memory leak" is that it's not useful to programmers.

A more useful definition is memory that remains allocated unintentionally.

How is that more useful? According to your definition, languages have nothing to do with memory leaks, pro or contra.
I think programmers mainly care about memory leaks because of the potential negative consequences of using up too much memory -- programs crash or fail, or services frequently restart, or you have to provision additional costly server resources to keep things running well.

If your program continuously allocates memory that it doesn't free and doesn't serve a useful purpose, it doesn't really matter if it's, e.g., being referenced by some cache or just completely unreferenced -- it's still causing the same problem and has the same solution (that is, release the memory when you're done with it -- whether that's by calling "free" or clearing the reference, or whatever your memory management system has)

Memory leaks may not be possible but resource leaks can be.

Many languages have tried to attach "disposers" to garbage collection events. The more common term in this case is "finalizer". As far as I know, no language has ever had a general-purpose, robust solution that works so well that it can just be used. Many have tried, and they all end up either deprecated, determined by the community to be a footgun, or backed down to some specialized use case (like picking up after file handles that are GC'd but the expected thing is still that normal user code closes them and this is explicitly pitched as an undesirable fallback, not the thing you should do).

No, but static code analysis could do a good job at catching this (at least it does in C#).
In C#, the garbage collector will eventually call the destructor that will dispose the object. It just won't happen deterministically with the code.

I assume there would be similar logic for JavaScript but I haven't looked at the specification.

It is not always a mistake to miss the using keyword -- if you're taking ownership of the object, then you will dispose of it manually (potentially in your own dispose method).

I'm not aware of a way to control the GC behavior of an object in JS. The memory will be freed, and that's all. If something else needed to be done, I don't think there's a way to do it. Happy to be proven wrong though.
You can kinda use a WeakMap to later detect if an object has been dereferenced, and thus run some sorta cleanup routine.
How? WeakMap (and WeakSet, more directly) can detect if an object has been collected only if you have a reference to pass to it. If you have reference to the object in a variable or data structure, it won't have been eligible for collection in the first place.
You can't use a WeakMap or a WeakSet, but you can use a WeakRef or a FinalizationRegistry.
I did not know about these things. Looks cool!
(comment deleted)
That situation already exists today, but is inconsistent: some things need .close() and others .dispose() and yet others .destroy() and then there's things like .end() and .reset() and .disconnect() and .revoke() and .unsubscribe(). All of those and more already exist in JS code today (just off the top of my head) with notes in library READMEs or other documentation to call them or possibly face resource leaks. The `using` keyword suggests a common name for these sorts of functions, [Symbol.dispose](), and provides lovely syntax sugar for try/catch/finally to make sure such things get called even in failure cases, and even provides suggested built-in objects DisposableStack and AsyncDisposableStack to help in managing multiple ones and transitioning older APIs like the many names I've seen, mentioned here.

Even better, right now lint tools have to individually case rules specific to each library given the variety of names, but lint tools can make a nice global rule for all objects implementing [Symbol.dispose]() (just as they can warn when a Promise is left unawaited).

Eslint will probably have a rule about it. As `call a function return promise without using the result` have a relevant rule today.

Probably it will block the pattern that `call a function that returns disposable object without using using keyword`...etc.

There's a conversation I had with Ron Buckton, the proposal champion, mainly on this specific issue. [1]

Short answer: Yes, Disposable can leak if you forget "using" it. And it will leak if the Disposable is not guarded by advanced GC mechanisms like the FinalizationRegistry.

Unlike C# where it's relatively easier to utilize its GC to dispose undisposed resources [2], properly utilizing FinalizationRegistry to do the same thing in JavaScript is not that simple. In response to our conversation, Ron is proposing adding the use of FinalizationRegistry as a best practice note [3], but only for native handles. It's mainly meant for JS engine developers.

Most JS developers wrapping anything inside a Disposable would not go through the complexity of integrating with FinalizationRegistry, thus cannot gain the same level of memory-safety, and will leak if not "using" it.

IMO this design will cause a lot of problems, misuses and abuses. But making JS to look more like C# is on Microsoft's agenda so they are probably not going to change anything.

[1]: https://github.com/tc39/proposal-explicit-resource-managemen...

[2]: https://stackoverflow.com/a/538238/1481095

[3]: https://github.com/tc39/proposal-explicit-resource-managemen...

I have some reservations about the specification - it looks like there are a lot of caveats to how it can be used. For example, it's allowed in for and for-of loops, but not in for-in loops. Also looks like destructuring will not be allowed.

    using res = getResource(); 
    const { x, y } = res; // ok
    
    using { x, y } = getResource(); // error
when would you want to do that?
(comment deleted)
When there’s a group of resources with a non-trivial disposal order but there’s no explicit manager object, for example.
(comment deleted)
Considering this limitation, I wonder if the syntax could have been something else than assignment. Like maybe:

  using getResource() as res;
I agree. If it's assignment then it should work the same as assignment (from the users perspective), otherwise it should have a different syntax.
Also seems like there would be use cases where you don't even want to assign the result, you just want the destructor to run at the end of the scope. Something like:

  using someMutex.lock();
With the assignment syntax it's not obvious to me whether that's allowed.
You assign it to a variable that is then disposed of once it's out of scope?
That's strange, what's the motivation behind not allowing destructuring? Seems very prudish.
It's ambiguous. Are you disposing of the object being destructured or the destructuring results? How do you make the distinction?

Much easier to not do it.

Not sure if this would produce more issues, but they could ignore c# and use something like:

  const using x = getX()
  const {using prop1, prop2} = using getX()
  void using getX()
  // x, prop1 and 2 anonymous Xs to dispose
Instead of giving `using` the power / the burden of variable creation. This also solves `using mutex.lock()` mentioned elsewhere itt.
To me it seems like disposing of the object being destructured is the natural solution. But I can see where others will disagree.
There is literally an example for using destructuring in the linked article. So it is supported.
Can you clarify where it indicates it's supported i.e. which specific example? I re read and didn't see it myself, seems others are missing it.

To be fair I don't see it not being supported either by the examples.

The last example:

    {
      await using { connection } = getConnection();
      // Do stuff with connection
    } // Automatically closed!
edit: according to the ES proposal issues, this might be something that doesn't actually exist, on purpose (it's rather ambiguous as to what will be disposed of): https://github.com/tc39/proposal-explicit-resource-managemen...
Thanks! Yes that example is subtly different.

I did also click through to the proposal but didn't see that issue. It does seem like it's still unclear what's intentional or not.

> not in for-in loops

What would that even mean? The binding in a `for-in` can only ever hold a string, which can't be disposable. With a `for` or `for-of` the binding can hold an object, which is something you could dispose of.

This looks very useful, as it's already useful in other languages.

I found the syntax a bit confusing though. `await` comes before a promise, except in this case. I wonder what the reasoning behind the syntax was.

Right, given that await x is an expression which could already resolve to an object with a dispose function, it seems like ‘using await x’ would just work, without needing special ‘await using’ shenanigans.
It's to distinguish whether to call res[Symbol.dispose]() or await res[Symbol.asyncDispose]() at the exit.
You’re right, that is a curious design choice.

For what it’s worth, I always thought this syntax for await felt more natural:

    let await user = getUser()
But now we have it both ways for different use cases and it’s weird.
That syntax wouldn’t let you use await as an expression: f(await p) or (await p).x, etc
Does this imply you can’t have `using` in expressions?
I agree. I feel like `using async x = ...` would read much more nicely and avoid the confusion.
It's marking that the disposal itself, when it comes time, needs to be awaited.

Keep in mind, await may need to be in both sides:

    await using connection = await getConnection()
If it was `using await` that might give some confusion if it was awaiting the left or right side or the future disposal.

Similarly, it gets fun combined with AsyncIterable:

    for await (await using connection of getAllConnections()) { }
Okay, now it is official, JS/TS becomes a C# clone ;)

no surprise considering the TS people

but using keyword will be in js eventually as it is in stage 3 now
Nominal types versus structural types is basically the main distinction at this point (if we ignore .net, etc). I like both languages, happy to see them converge while keeping their defining characteristics
Where do C# anonymous types fall? From a functional perspective, they are a purely structural type.

Here's a snippet of C# I wrote recently to transform some JSON:

    var transformed = media.Select(m => {
      var parts = m.Split('/');
    
      return new {
        addedAtUtc = "2023-05-15T05:59:31.398Z",
        originTripUid = parts[4],
        path = $"{parts[4]}/{parts[5]}",
        rank = "",
        size = 103978,
        stored = true,
        type = "document",
      };
    })

    File.WriteAllText(
      Path.Combine(Environment.CurrentDirectory, "output.json"),
      System.Text.Json.JsonSerializer.Serialize(transformed)
    );
It would be easy to mistake this as JS at a quick glance.
Anonymous types are nominal types (like all others in .NET). They are just auto-generated and auto-named.

Duck Typing at runtime is possible and to a degree checkable and compile-time.

I get that; that's why I qualified it as a "functional perspective" because even though the underlying implementation is an auto-generated, auto-named type, the functional usage of it is a structural type (with limitations).
They aren't structural because the following doesn't work in C#, but would work (with tweaks to make it valid code) in a structurally typed language such as TypeScript:

  var transformed = media.Select(m => {
        var parts = m.Split('/');
    
        return new {
    addedAtUtc = "2023-05-15T05:59:31.398Z",
    originTripUid = parts[4],
    path = $"{parts[4]}/{parts[5]}",
    rank = "",
    size = 103978,
    stored = true,
    type = "document",
     };
  }).ToList();
  
  transformed.AddRange(otherMedia.Select(m => {
        var parts = m.Split('/');
    
        return new {
    addedAtUtc = "2023-05-15T05:59:31.398Z",
    originTripUid = parts[4],
    path = $"{parts[4]}/{parts[5]}",
    rank = "",
    size = 103978,
    stored = true,
    type = "document",
    documentType = parts[6]
     };
  }));
Adding "documentType" breaks in C# but would work in a structurally typed language as a structural type checker can see that the second result fulfills all of the necessary properties. Doing this in C# would require creating an interface and being explicit.
Runtime type systems: TS has none; C# has extensive RTTI

Function dispatch: TS is purely dynamic; C# has both static and dynamic

OOP: Mandatory in C#, optional in TS

Variance: Implicit in TS, explicit in C#

Numeric types: TS has one (number); C# has the entire signed/unsigned/float x 8/16/32/64-bit matrix

There's really no intentional effort to converge them.

Your list of differences is correct but not really what I meant. The points you listed are mostly due to JS runtime constraints. The choice of nominative vs structural type system is purely a design choice by the typescript team.
And this is a good thing. I would love to see something like a proper C# instead of a hacky TypeScript. It would be so much more productive.

JavaScript is a cool dynamically-typed language though. C# is on another side of the spectrum: it is super cool by being fully strongly-typed. But TypeScript is something in the middle and this uncertainty gives pains.

But that is the niche of TypeScript. It supports a dynamic typed language by enabling some but not all benefits of typed languages.
I find that Dart rides that middle line much nicer than typescript does, at least for now. As of Dart 3, you can now have sound null safety, and sound type safety for 100% of your code, if you want. The recent addition of records also makes working with some of javascripts methods with multiple return types better, as that is one of the things dart is still missing, union types. Though, I tend to prefer dart over typescript just because of not having to deal with the node ecosystem at all.
(comment deleted)
Too bad Dart is only used in Flutter.
That’s your choice, I suppose. I use it for everything from flutter to CLI to Cloud Functions to WebUi to daemons in tiny containers.
Any way to use Dart with React in a .jsx way? It looks like Dart has exclusive support for Flutter, and that's it.
(comment deleted)
Man, the context of question, is how to use Dart in a Typescript/React application (because of popularity of ReactJS ecosystem, of course).
> That’s your choice, I suppose.

Not really.

For personal projects or if you're a freelancer sure. Once you work in a company/startup you can't go and implement things in whatever you like.

It's way easier to convince any manager that Dart/Flutter is a good choice for a mobile app because of the tech advantages than in anything else because of the pool of available developers.

Do you use Dart for anything other than Flutter though? I'm a huge fan of Dart, but I don't see it getting much server usage. Like, could I deploy a Dart CloudFlare worker?
yes, cloud flare even has a guide on their docs if you go to the Workers > Platform > Languages section. Also, dart 3 is previewing wasm support, and could be used through a wasm worker.
Honestly typescript is fine, just give me:

Standard library that isn't worthless - no more npm package spam.

Type knowledge in the runtime. It doesn't have to be typed, just make it so I don't have to do crazy checks on properties before casting to a class.

Imports without webpack.

> "just make it so I don't have to do crazy checks on properties before casting to a class."

You do have 'instanceof' at runtime, so unless you mean some other type of 'class', casting to a class is really a non-issue (and you don't really 'cast'... since dynamic typing and all). If you mean some more advanced concept (e.g., structural type runtime checking), think how you would do it in statically typed languages (is it really better?).

Instance of doesn't work sometimes and you have to write explicit property checks. I forget the details because I'm by no means a typescript expert, but I remember being very frustrated by the fact I couldn't just use instance of.
It doesn't work when objects cross global scope boundary (e.g. you get an object from a frame), there's no fixing that, by design the namespaces are different and unrelated.
(comment deleted)
instanceof only works with JavaScript classes. Not on regular objects that just structurally conform to a type or interface.
(comment deleted)
Classes aren't the only way to define types?
Structural typing is a thing.. also there aren't really classes in js in the Java sense, it's all prototype chains.
instanceof only works for inherited types, usually expressed with the class keyword by TypeScript users. It's entirely worthless if you're not using them. Class is the only construct in TS that is both a type and a value.
JavaScript has half decent object construction syntax that avoids the nesting hell of classes:

    function Foo(s) {
        this.s = s
    }

    Foo.prototype.bar = function() {
        console.log(this.s)
    }

    const baz = new Foo("Baz")
    baz.bar()
But it seems Typescript can't handle it?
Well there are 2 issues:

(1) imperative style mutation of the prototype is runtime dependent, and typescript can't really rely on runtime behavior.

(2) Foo has two incompatible types in JS, it is both `(s) => void` and `new (s) => Foo`, typescript by default will assume the first signature (which is usually what people want).

In any case, you can specify the types (though in 99% of the cases, just use a class):

   interface Foo {
       s: string;
       bar(): void;
   }

   const Foo = function(this: Foo, s: string) {
       this.s = s;
   } as {
     new (s: string): Foo;
     (this: Foo, s: string): void;
   };
   
   Foo.prototype.bar = function(this: Foo) {
       console.log(this.s);
   }
   
   const baz = new Foo('Hello');
   baz.bar()
> though in 99% of the cases, just use a class

While that is a fair statement, I've been reading a lot of Typescript code lately and it seems in 99% of cases the selected solution is to use top level functions and globals to avoid the nesting hell class introduces, albeit introducing the global hell in the process. In practice, 'just use a class' doesn't overcome the human element.

Javascript was on the right track originally, but then veered off into the weeds for some reason. I appreciate you pointing out how it can be done. Although the solution is very much second class citizen, sadly. This certainly isn't going to wean developers away from global hell. Hopefully Javascript/Typescript can make some strides in improving this going forward.

I'm not sure what improvments are you suggesting.

We are talking about runtime class creation with dynamic methods, while still adding proper static types. There aren't many language allow this level of flexibility.

You can probably disallow prototype use with existing tools if that's desired, but it is an advanced tool for advanced use cases (which still exist).

We are talking about how poor syntax pushes developers into some strange corners to avoid having to deal with the poor syntax. class ultimately gets you to a decent place, but forces the code into one big giant mess that nobody wants to look at ever again. Prototype function assignment gets you to the same place, with better syntax, but falls short for other reasons.

Given:

    class Foo {
        constructor(x) {
            this.x = x
        }

        bar() {
            return this.x
        }
    }
maybe it becomes:

    object Foo(x) {
        this.x = x
    }

    function Foo.bar() {
        return this.x
    }
> Standard library that isn't worthless

This is exactly what bugs me about TypeScript. The language itself is fine—some annoying things, but generally they’re due to having to run on top of JS. But Microsoft had the chance to fix JS’s terrible standard library. And they didn’t!

Give io-ts a try for the runtime type checking. We’ve used it in prod for a few years now and it’s pretty solid.
> Imports without webpack.

https://caniuse.com/?search=import

Imports through the browser do, but I'm wanting a single file bundle with all those imports so I'm not relying on a browser feature to deliver the content over N requests.

I know that's less of a concern nowadays with modern http, but guess who still supports ie11

I'm sorry you are stuck still supporting IE11. You could use an AMD loader and use imports in Typescript configured to transpile AMD modules. Most AMD loaders were tiny. The benefit to AMD modules in your case is that they don't need webpack to bundle, the (hideous) module format was designed to be bundled by raw concatenation and the Typescript compiler will still even do that for you with the --outFile parameter (or "outFile" tsconfig.json setting).

It feels like terrible advice to give someone, given ESM is far superior to AMD, and this would be going backwards in time, but in your case you are already blighted by the disease and it is advice that might staunch some of the bleeding.

What's missing in the standard library though? I hear this complain often, but when I use a library it's always for a specific use case and not something that I would expect to be in a standard library.
it's really not a c# clone. If anything JS is still more similar to python that it is to c#.

JS already has 'with', so 'using' seems like to closest semi-standard keyword to use.

Of course this does not change the fact that it is a reserved keyword, but "with" is prohibited in strict mode and as such also in any ES Module. I assume this also applies to any TS module regardless of the compiler.

But since TS is supposed to be a superset of JS, of course this does not change anything

when you substract everything what C/C++/Java is, this will be interesting comparison what is closer ;)
That was my first though, but a) it seems that the `using` syntax is not exactly the same as in C# (unless the old C# I used to use) and b) still I wouldn't mind, C# is a great language
wasn't that the goal from the start
Really happy that this feature is finally seeing the light of day. When I switched my code from callbacks to promises in node 10 years ago, it seemed like the most natural thing to add that would finally resolve all the resource management fears that node has had with exceptions and error handling.

We implemented a solution in bluebird at the time that used closures (it started with a simple sketch https://promise-nuggets.github.io/21-context-managers-transa... and ended up much more elaborate http://bluebirdjs.com/docs/api/promise.using.html) but this variant is so much cleaner without all the nesting and I'm glad its finally making its way into the standard.

I wonder how does it work with destructuring? The last example extracts `connection` from the returned object:

await using { connection } = getConnection();

If you are used to Rust semantics, you'd expect that the whole thing is disposed, apart from still living objects. Reading this line I am immediately afraid that the whole object is disposed immediately (or right on the next line), the callback is called and your `connection` a couple of lines later will be closed already.

Will await using actually perform the await at release, i.e. at the end of the scope? Could add an unexpected event loop tick around a curly brace (so if you schedule something after the await but before the end of the scope, it will look like the scheduled thing will always happen after dispose, but it could happen before.)
If a company would embrace, extend, and extinguish JavaScript, this is what it would look like.
It's a javascript feature in proposal (stage 3) being made available in TypeScript. You have got it backwards.
Although given the naming it's very much from C#.
It's just cross pollination across popular languages.

So Sun microsystems is trying to EEE Microsoft since C# is mostly from Java?

If you're going to steal anything from C++, RAII is a good thing to steal.
(comment deleted)
They didn't steal it completely since it doesn't happen automatically. You can still forget to clean up and leak files or threads or what have you.

I continue to be disappointed that only Rust and C++ have automatic destruction. People already regularly forget to use Java's try with resources or Python's with, or Go's defer.

Isn't the point of computers that they can remember stuff for us? Everyone agrees automatic cleanup is the way to go for memory but not any other kind of resource?

RAII doesn't work with a (non-refcounting) GC is the issue: when the scope ends, you've no idea how many references there are and which of these are live.

With static typing you could opt into affine or linear types and those could get special cased by the compiler (to ensure a single reference is possible, and trigger the drop in case of affine types), but first you do need static typing (that's not javascript), and then you need to update the entire language to correctly handle the new affine or linear behaviour.

And that can lead to a lot of weird shit with languages not designed for that (especially linear typing).

JavaScript development looks really strange.

  const resource = {
    [Symbol.dispose]: () => {
      console.log("Hooray!");
    },
  };
Why not just

  const resource = {
    dispose() {
      console.log("Hooray!");
    },
  };
Why introduce this weird Symbol stuff?
Backwards compatibility. This can't conflict with existing types that have `dispose` on them already. Symbols are private, meaning two symbols with the same name are not the same symbol. Symbols use object reference equality, not value equality. So `Symbol.dispose` is the only way to refer to the dispose method they're looking for, without conflicting with any existing fields called "dispose".
(comment deleted)
> Symbol is a built-in object whose constructor returns a symbol primitive — also called a Symbol value or just a Symbol — that's guaranteed to be unique. Symbols are often used to add unique property keys to an object that won't collide with keys any other code might add to the object, and which are hidden from any mechanisms other code will typically use to access the object. That enables a form of weak encapsulation, or a weak form of information hiding.

> Prior to well-known Symbols, JavaScript used normal properties to implement certain built-in operations. For example, the JSON.stringify function will attempt to call each object's toJSON() method, and the String function will call the object's toString() and valueOf() methods. However, as more operations are added to the language, designating each operation a "magic property" can break backward compatibility and make the language's behavior harder to reason with. Well-known Symbols allow the customizations to be "invisible" from normal code, which typically only read string properties.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Essentially if they use dispose() like you mention, what happens if someone already has code that has dispose? And in future, avoid people accidentally calling these.

Using `using` is opt-in. So if you have code that has dispose function used for other purposes, rewrite that code before using `using`.
That's case for this particular feature, but the approach has been with know symbols and is how they introduce new features into the app.

Also, you are assuming the consumer of a class is same as someone that used / uses the class. What if the library author has dispose and someone wrongly use using with it?

Kind of the other way round. If you have code that has a function named `dispose`, it will keep on working just fine, nothing needs to be renamed (not now, and not in the future when you want to start using `using`).

The symbol `Symbol.dispose` is special, unlike any normal string or identifier, because it didn't exist until the new standard, so existing code can't have been using it. This guarantees a level of backwards compatibility that wouldn't exist otherwise.

(comment deleted)
This would break many sites on the web, if they roll this out as a native feature of JS. Importantly, JS generally cannot break backwards compatibility.
My guess is backwards compatibility. There might be applications out there that have a dispose method for different reasons. Symbol.dispose is literally impossible to be already in use, since symbols are guaranteed to be unique.
I'd imagine dispose is a common function name and being dynamically typed there is potentially a lot of code out there that already has a "call dispose if it exists on this object" pattern.

Vs this won't break anything existing.

Imagine that resource inherits from a class with an existing method called dispose. What happens then?
(comment deleted)
And how you or JavaScript know that dispose is an automatically called function?
> Why introduce this weird Symbol stuff?

Because this potentially break every existing object with a `dispose` symbol.

That is exactly why symbols were added in ES6, and what they're used for. You're just 8 years later.

Symbols are used for all kinds of "built in" functions, and they were not "introduced" for this feature. As symbols are unique, they don't break/interfere with existing methods/properties. Therefore, if someone already has a dispose function named dispose, the new feature won't interfere with a method called dispose method and vice-versa.

"Well-known" symbols include things like Symbol.iterator that make "for...of" loops possible, Symbol.asyncIterator for async for loops, Symbol.hasInstance for instanceOf, Symbol.toStringTag for toString, and others.

[flagged]
It's for people who like static types, so this is static types for javascript + polyfills.
I know what's TypeScript - just not getting why would you want to use it
(comment deleted)
In a complex codebase, it’s very helpful! It’s either TS or jsdoc. They are lifesavers sometimes.

Knowing what expects what and what you should expect in return. It helps you catch those minor bugs.

means you never had control over your code to begin with. adding complexity on top of complexity is not an answer
Well, sometimes you happen to be apart of ≈10 year old codebases with lots of people who've been working on it and is no longer there.

At that scale, TS is a godsend!

Want to use it as opposed to what?

If you can't understand why people would want to use typescript instead of javascript then you're not trying very hard. And a lot of people are in situations where they need to use one of those.

why should I be tryharding to buy into something myself? just means product/marketing sucks
You should be trying to understand, not trying to buy in.

It's really obvious why, even if you personally get no value from types.

no, it is not obvious. costs way too much
The last example is surprising:

  {
    await using { connection } = getConnection();
    // Do stuff with connection
  }   // Automatically closed!
The object returned by getConnection() is destructured to extract the field named connection and assign it to a local variable with the same name. The containing object presumably continues to exist anonymously so its Symbol.asyncDispose function will still be called when it goes out of scope.

Can anyone explain which language rule guarantees this behavior in C# and in TypeScript?

C# doesn't allow you to destructure a single property and as far as I can tell based on a quick test it doesn't allow you to combine a using declaration with destructuring. I agree that this is surprising syntax... as a C# dev my first interpretation of that code would be that connection is the thing being disposed since it's the variable being declared. I wonder if js/ts destructuring has always silently held a reference to the containing object or if they added that behavior to make this use case work?
> C# doesn't allow you to destructure a single property and as far as I can tell

https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYAMBYAUBgRj2Nw...

Your code is destructuring two properties and discarding one of them. It doesn't work with a single property: https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYAMBYAUBgRj2Nw...

I think that records don't generate a deconstruct method when they only have one property, but even if you manually define one you'll get an error on `var (varName) = ...`

I thought the same and I don't see motivation from it on the TC39 spec page [1] but I do see they have several examples that make use of the feature.

It makes me realize I've never really considered the memory implications of doing something like:

    const { someSmallPart } = getSomeMassiveObject();
I don't really know the lifetime of "massive object". Probably a bad on my part for not knowing if Javascript garbage collection is allowed to notice that the only thing with a reference is `someSmallPart` and could therefore release any other parts of the massive object. If the answer to that is "no" then there is no problem with the above pattern.

If the answer is "yes", then things could get complicated. e.g.

    async function getComplicatedObject() {
      const db = await db.getConnection( ... );
      const f = await file.open( ... );
      return { db, f, [Symbol.asyncDispose]: () => { 
        db.close()
        f.close()
      }
    }

    {
      await using { f } = getComplicatedObject();
      // ... more stuff with awaits where the GC might decide `db` isn't used so it can clean
    } // I would not expect a null ref error on `db` here when `asyncDispose` is called
I mean, you can handle the above even if the GC is allowed to be smart and discard `db` - it just makes it significantly more complicated and requires clever book-keeping.

1. https://github.com/tc39/proposal-explicit-resource-managemen...

Isn’t it natural that async dispose property would pin an entire object at `using` scope? If there’s no pin via using, then GC should be able to collect unused contents anytime.
The article is wrong. That's not going to be legal, specifically because it's confusing which object is being disposed.
I'm pretty sure that example won't work and is based on an old version.

But assuming a javascript implementation where it works, I don't know what you mean by "language rule". The spec says what "using" means, and it doesn't require any behavior the language doesn't already have. The old spec said "When try using is parsed with an Expression, an implicit block-scoped binding is created for the result of the expression.", and the new one says "When a using declaration is parsed with BindingIdentifier Initializer, the bindings created in the declaration are tracked for disposal at the end of the containing Block or Module". Then the spec has an example implementation written in javascript, with the value being added to a (non-user-accessible) list. https://github.com/tc39/proposal-explicit-resource-managemen...

Is that the language rule you wanted?

It's not clear to me how / if this works with closures.

I expected that it would be part of a block initializer. e.g. `using x { /* scope where x exists */ } /* x was disposed when the block ended */`. This would also make it clearer that it can work even without assignment or with destructuring assignment (the outer object is disposed after the block even if it is not used).

I wonder why they decided not to do it like that.

Came here to ask the same question
C# has that syntax for using (the using block) but it's slowing being phased out in favor the using statement like this. It's a big cleaner and closer to the needs of the programmer this way.

It's not much different then let -- you're defining a block scoped variable that just happens to call the dispose symbol function when the block ends.

> It's not clear to me how / if this works with closures.

If you elaborate on what working with closures and not working with closures would look like, I could try to answer that.

Interesting, what exactly is it I am looking at?

const resource = { [Symbol.dispose]: () => { console.log("Hooray!"); }, };

An object called resource, consisting on a anonymous function that logs Horray to the console. This is all fine and I am used to that.

The interesting part that stuck with me is "[Symbol.dispose]:". Why is it in a Array? What does this exactly do? It marks the anonymous function as disposable at the end of the code block? Correct?

{ [Symbol.dispose]: () => {...} }

It's not an array, but a way of using a variable as a key in an object.

const name = "key"

const example = { [name]: "value" };

This would result in

const example = { key: "value" };

So if we know Symbol.dispose in advance we can simplify the code? Why did they make it so unwieldy especially since it is a new functionality not requiring backwards compatibility hacks?
> So if we know Symbol.dispose in advance we can simplify the code?

Symbol.dispose is not a string, but a symbol. Its value cannot be written out in the code, only referred to indirectly (that is basically what symbols are for), nor is there special syntax for properties with symbol keys like there is for properties with string keys, because that would be a nonsensical proposition due to the aforementioned fact. Thus, you cannot just write the property name directly, you have to use the computed property name syntax: [Symbol.dispose].

It wouldn't be any different:

    const dispose = Symbol.dispose
    const resource = { [dispose]: () => {...} }
> So if we know Symbol.dispose in advance we can simplify the code?

Symbol.dispose is Symbol.dispose. It's a singleton object and that's the sole reference to it.

> Why did they make it so unwieldy especially since it is a new functionality not requiring backwards compatibility hacks?

1. Bare names in object keys are strings, so there needs to be an "escaping" syntax for non-string keys, that is what the brackets have done since 2015, in order to support the addition of...

2. Non-conflicting keys, which are what symbols are: symbols are guaranteed not to conflict with any string, and even to be unique unless they're created using `Symbol.for` (which accesses and stores into a global registry, this allows adding protocol methods to all objects without the risk of breaking existing any (by triggering new and unexpected behaviour).

It feels like this sort of syntax convention could be used to bolt on all sorts of interesting features to the language. Like, at this point, this is basically a sort of compiler hack to add features rather than really do any typechecking.
It's based on an ECMAScript proposal that is going to be added to mainstream javascript. Typescript is just adding it faster before the browsers.
Stage 3 is "implement it and provide feedback based on real implementations" so in some respect Typescript is adding it at the same time as browsers (and is one of the parallel implementations expected to provide feedback ahead of Stage 4).
By coming up with even more special-case syntaxes to replace "const"? e.g. you want to have something like "foo x = something.else" to replicate the syntax convention of "using x = ..."? I'm not sure I understand what you're referring to.
Reminds me of Resources from Scala. I keep seeing features being carried over from language to language--I wonder if we'll eventually reach convergence where all languages, perhaps within several broad groups like OOP vs FP, look the same with minor syntactical differences.
You're describing entropy. Properties and values distribute in the space until we reach equilibrium. Making all languages mostly the same. Unfortunately this doesn't mean we've found the ultimate programming language. We simply stopped innovating, believing there's nothing to innovate about (while actually we're in a local maximum).
In the 2000s, all the popular languages had C-like syntax and many had similar semantics. But now we're in a world with more diversity (Rust, Swift), so I think things are less convergent than they used to be.
This is a good proposal, but I'm worried about the choice of syntax. Simply swapping `const x = ...` for `using x = ...` feels like it's not a "weighty" enough piece of syntax for such a complicated automatic disposal feature. I understand that the idea is to make it very low-effort and easily applicable to multiple resources, but I think intuitively I wouldn't exist "using x =" to call any sort of complex code "automatically". Instead, I think the block-scoped proposal in the "withdrawn" section makes a lot more sense

    using (x = ...) {
      console.log(x.whatever)
    }
Thoughts? I feel like the current proposal has a lot of the same issues I dislike with C++ (a focus on "terser" syntax obscures what is actually happening e.g. initializer lists don't actually tell you what constructor they're calling).
The forced nesting that particular proposal makes it less ergonomic (especially for scopes with multiple such resources), and would bring very little benefit over a simple library-based approach (instead of a language feature), something like the following for your example:

    using(..., x => {
      console.log(x.whatever)
    });
I don't see anything to suggest "using" bindings can't be closed over just as with any other lexical binding. Indeed, your example does so. Nothing in the proposal would seem to prevent returning such a closure from a function that acquires a resource, or a collection of same, and being able to comfortably use the bound resources without losing the guarantee they'll be properly disposed once no longer in scope.

Granted it still could get screwed up. But if it doesn't, and assuming of course that I've understood it correctly to begin with, I suspect a comfortable way to think about it will be as a means of hinting to the GC how it should handle cases where free() alone doesn't suffice.

I think you two are actually on the same page— their critique is of the nested block version of the proposal that OP prefers, not the the actual stage 3 proposal.
It seems fine, the increased scoping would just be bothersome.

The annoyance to me is that like all scope / defer constructs it gets wonky when paths split between a success and a failure path, and you do want the resource to escape in the former case. That's an area where RAII really shines.

> The annoyance to me is that like all scope / defer constructs it gets wonky when paths split between a success and a failure path, and you do want the resource to escape in the former case. That's an area where RAII really shines.

Is this even supported by the proposal in question though? I don't see anything here that indicates any sort of escape analysis or object tracking is performed—resources are always cleaned up lexically so `using foo = handle(); return foo` will always call foo to be cleaned up, just like the equivalent `finally` statement.

That's correct, there is no escape analysis or anything like it. Though there is a way to handle the "I want to return a resource" case: the DisposableStack class. You can call `stack.move()` to "move" ownership of a resource (and rely on the place you're passing it to to do cleanup). E.g.

  using stack = new DisposableStack();
  stack.use(resource());
  if (condition) {
    // the resources will _not_ get disposed automatically by this return
    // it's the caller's responsibility to call `stack.dispose()`
    return stack.move();
  }
> Is this even supported by the proposal in question though?

No and that’s exactly the problem.

If you “using” a resource and you leak it, you’ve now handed off a disposed off and probably useless resource.

If you don’t, and don’t, you have a resource leak.

In trivial cases this is fine-ish, just do whichever is needed, but when there are conditional paths internally (sometimes you signal an error others you return normally) you can’t “using” at all, you’re back to try/except and disposing by hand.

RAII does not have that issue, it does the right thing every time.

A few languages solve this by having additional constructs for these specific scenario (e.g. errdefer), though that requires more direct integration with the language’s error reporting, and thus forbids (or at least does not work with) alternate ones.

They took the syntax (almost) straight from C# where it works great. We had only block-scoped using for a long time and that sucked; the variable declaration style is definitely better and we readily adopted it when it became available.
C# had only block based using for a long time. It made a lot of code unreadable because the using statements introduced a ton clutter. The newer syntax is way better.
`with` blocks in Python become very ugly when nested, I'm glad it's a single line personally
Nice to have, but normaly you wrap file / database / whatever into a function or a lib, that handles erors, file handle closes, etc. Currently i did not see much usecases for me. But lets see how (crazy?) it will be used.
I expected the example to be less code. Not more. Other than making it more familiar for C# devs what value does it add?
It pushes the extra code to the function. If you're consuming a library, that's code you never have to write. Even if you are writing the code, it moves the complexity out from your business logic so it can focus on the important things rather than cluttering it up with tidying up resources.
Using libraries we already don't have to write cleanup code. What does the new keyword add that we can't already do? https://node-postgres.com/apis/pool#poolquery
From what I can tell from that documentation, you do still have to remember to call `client.release()`. There's even a big warning saying "You must release a client when you are finished with it."

Presumably you would want to wrap that in a try/finally so that you don't accidentally not release the client if there is an exception.

You would go from:

  const client = await pool.connect()
  try {
    // Do stuff. Possibly throw.
  } finally {
    client.release()
  }
to

  await using client = pool.connect()
  // Do stuff. Possibly throw.