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?
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".
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.)
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.
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.
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.
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"?
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).
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.
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.
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).
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.
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
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.
It’s intentional. The grammar production in the proposal is parametrized as ~Pattern (a parameter introduced in the proposal), which means it does not allow destructuring.
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.
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.
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
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.
Sure, but that would be opt-in if it were ever implemented. I think both styles of type system have their pros and cons. I have loved the cross-pollination between C# and TS with great ideas flowing in both directions.
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.
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.
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.
> "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.
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.
(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):
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.
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
}
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!
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.
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
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
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.
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.)
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).
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".
> 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.
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.
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.
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.
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.
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.
{
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?
If I understand your question right, for C# look at the following article, last paragraph regards object which leave the scope of the using (C# does not have the JS "var" but only the JS "let" behavior, so it is slightly different here).
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?
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.
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.
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...
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).
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.
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?
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].
> 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.
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:
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.
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.
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.
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.
377 comments
[ 0.24 ms ] story [ 771 ms ] threadI 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...
Well, half of it at least :)
Python has both enter and exit, while this proposal only has the exit half.
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
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.
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).
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()
So speaking specifically of a new TypeScript feature is justified here.
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.
If you use react, it can get a lot more interesting[0]
[0] https://twitter.com/JLarky/status/1669741878324113408
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).
Here are common pitfalls: https://auth0.com/blog/four-types-of-leaks-in-your-javascrip...
A more useful definition is memory that remains allocated unintentionally.
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)
For example, if you're using three.js, various WebGL resources need to be manually disposed: https://threejs.org/docs/index.html#manual/en/introduction/H...
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).
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).
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).
Probably it will block the pattern that `call a function that returns disposable object without using using keyword`...etc.
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...
Much easier to not do it.
To be fair I don't see it not being supported either by the examples.
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.
https://tc39.es/proposal-explicit-resource-management/#prod-...
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.
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.
For what it’s worth, I always thought this syntax for await felt more natural:
But now we have it both ways for different use cases and it’s weird.Keep in mind, await may need to be in both sides:
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:
no surprise considering the TS people
Here's a snippet of C# I wrote recently to transform some JSON:
It would be easy to mistake this as JS at a quick glance.Duck Typing at runtime is possible and to a degree checkable and compile-time.
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.
[0]: https://www.typescriptlang.org/docs/handbook/2/everyday-type...
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.
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.
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.
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?).
(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):
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.
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).
Given:
maybe it becomes: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!
https://caniuse.com/?search=import
I know that's less of a concern nowadays with modern http, but guess who still supports ie11
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.
JS already has 'with', so 'using' seems like to closest semi-standard keyword to use.
But since TS is supposed to be a superset of JS, of course this does not change anything
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.
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.
So Sun microsystems is trying to EEE Microsoft since C# is mostly from Java?
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?
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).
> 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.
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?
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.
Vs this won't break anything existing.
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.
"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.
Knowing what expects what and what you should expect in return. It helps you catch those minor bugs.
At that scale, TS is a godsend!
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.
It's really obvious why, even if you personally get no value from types.
Can anyone explain which language rule guarantees this behavior in C# and in TypeScript?
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
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) = ...`
It makes me realize I've never really considered the memory implications of doing something like:
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.
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...
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?
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.
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.
If you elaborate on what working with closures and not working with closures would look like, I could try to answer that.
This link might work when TS 5.2.0 is released.
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]: () => {...} }
const name = "key"
const example = { [name]: "value" };
This would result in
const example = { key: "value" };
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
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].
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).
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.
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.
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.
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:
to