48 ms·
TypeScript 5.2's new keyword: 'Using'
- krona 3y agoThis would make my webassembly memory management much cleaner and ergonomic. Same goes for WebGPU, probably.
- sirwhinesalot 3y agoUseful in C# and should be just as useful in TS. Nice.
- LordN00b 3y agoAs long as we don't have to do the dipose dance (isDisposed/isDispoing booleans).
- bertylicious 3y agoThis awkward dance is due to destructors/finalizers calling `Dispose()`. Since JS doesn't have those, this shouldn't be an issue.
- pwdisswordfishc 3y agoFinalizationRegistry exists…
- manojlds 3y agoIt's been a while since I coded in C# but you just triggered some bad memories.
- samwillis 3y agoNice, 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-management#meeting-notes https://github.com/tc39/proposal-explicit-resource-managemen...
- epolanski 3y agoTypeScript 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.
- WorldMaker 3y agoThose 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.)
- whostolemyhat 3y agoThe article mentions that this proposal is in stage 3 for Javascript, so will probably be refined a bit more https://tc39.es/process-document/ https://tc39.es/process-document/
- RyanCavanaugh 3y agoThings 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".
- zokier 3y ago> 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.
- masklinn 3y agoTBF the `__enter__` hook is not the most useful.
- pwdisswordfishc 3y agoMore 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.
- aphexairlines 3y agoThis is a JavaScript keyword and there's nothing TypeScript-specific in the blog post.
- whostolemyhat 3y ago[flagged]
- belk 3y agotypescript is introducing it in a polyfill, which is going to be how a lot of people will first get to use the feature
- deleted 3y ago[deleted]
- tantalor 3y agoHow does the polyfill work? How does TS know the object can be disposed?
- masklinn 3y agoYou're telling it through the `using` statement?
- tantalor 3y agoI mean the implementation of the polyfill, in JS
- Dylan16807 3y agoThat 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.
- Drakim 3y agoIt'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"?
- rixtox 3y agoThere'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-management/issues/159 https://github.com/tc39/proposal-explicit-resource-managemen... [2]: https://stackoverflow.com/a/538238/1481095 https://stackoverflow.com/a/538238/1481095 [3]: https://github.com/tc39/proposal-explicit-resource-management/pull/168 https://github.com/tc39/proposal-explicit-resource-managemen...
- Nathanba 3y agowell maybe you want to explicitly free it later...
- deleted 3y ago[deleted]
- maxloh 3y agoJavaScript is a garbage collected language, so no memory leak is possible.
- Karupan 3y agoQuite 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 https://twitter.com/JLarky/status/1669741878324113408
- iainmerrick 3y agoIt'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.
- Karupan 3y agoTrue, “equivalent” was the wrong word to use.
- masklinn 3y agoIt'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).
- iainmerrick 3y agoYou’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.
- radq 3y agoI 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
- cerved 3y agowhen would you want to do that?
- deleted 3y ago[deleted]
- wruza 3y agoWhen there’s a group of resources with a non-trivial disposal order but there’s no explicit manager object, for example.
- deleted 3y ago[deleted]
- pavlov 3y agoConsidering this limitation, I wonder if the syntax could have been something else than assignment. Like maybe: using getResource() as res;
- kybernetikos 3y agoI agree. If it's assignment then it should work the same as assignment (from the users perspective), otherwise it should have a different syntax.
- pavlov 3y agoAlso 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.
- iansinnott 3y agoThis 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.
- jameshart 3y agoRight, 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.
- debugnik 3y agoIt's to distinguish whether to call res[Symbol.dispose]() or await res[Symbol.asyncDispose]() at the exit.
- osener 3y agoYou’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.
- saiojd 3y agoI agree. I feel like `using async x = ...` would read much more nicely and avoid the confusion.
- KwanEsq 3y ago
- oaiey 3y agoOkay, now it is official, JS/TS becomes a C# clone ;) no surprise considering the TS people
- dgellow 3y agoNominal 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
- CharlieDigital 3y agoWhere 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.
- oaiey 3y agoAnonymous 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.
- CharlieDigital 3y agoI 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).
- AshleyGrant 3y ago
- spion 3y agoReally 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-transactions.html https://promise-nuggets.github.io/21-context-managers-transa... and ended up much more elaborate http://bluebirdjs.com/docs/api/promise.using.html 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.
- oleganza 3y agoI 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.
- blixt 3y agoWill 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.)
- z3t4 3y agoIf a company would embrace, extend, and extinguish JavaScript, this is what it would look like.
- manojlds 3y agoIt's a javascript feature in proposal (stage 3) being made available in TypeScript. You have got it backwards.
- __alexs 3y agoIf you're going to steal anything from C++, RAII is a good thing to steal.
- deleted 3y ago[deleted]
- kaashif 3y agoThey 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?
- masklinn 3y agoRAII 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).
- vbezhenar 3y agoJavaScript 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?
- junon 3y agoBackwards 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".
- deleted 3y ago[deleted]
- manojlds 3y ago> 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/Reference/Global_Objects/Symbol 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.
- Lapsa 3y ago[flagged]
- Alifatisk 3y agoIt's for people who like static types, so this is static types for javascript + polyfills.
- Lapsa 3y agoI know what's TypeScript - just not getting why would you want to use it
- Alifatisk 3y agoIn 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.
- Lapsa 3y agomeans you never had control over your code to begin with. adding complexity on top of complexity is not an answer
- Alifatisk 3y agoWell, 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!
- deleted 3y ago[deleted]
- Dylan16807 3y agoWant 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.
- codewiz 3y agoThe 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?
- oaiey 3y agoIf 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). https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/using https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
- Merad 3y agoC# 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?
- metaltyphoon 3y ago> C# doesn't allow you to destructure a single property and as far as I can tell https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYAMBYAUBgRj2NwDcBDMAAgAoB9GK8gcwFMBKKgXiqlYHcqABVZgAzgHsoNAEQBbVjJgFM7ANwlUBAJw0WHDbjxhWAYwlg4w0ZOlbMVAHLkFjAJZRgVAIJt1QA= https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYAMBYAUBgRj2Nw...
- Merad 3y ago
- kybernetikos 3y agoIt'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.
- brundolf 3y agoCame here to ask the same question
- wvenable 3y agoC# 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.
- Dylan16807 3y ago> 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.
- clarkdale 3y agoWhat does the compiled JavaScript look like?
- Alifatisk 3y agohttps://www.typescriptlang.org/play?ts=5.2.0#code/N4KABGDGD2B2DOAXMBzApogSm+0CuATpGmALxgAUAlGQHxigQQEaGwPhMQDaAygJ4BbAEbQANgDoAJgEt4AB2jw0AXQBclGqXqMuTGAnFoJY6CgoByABLRoBAIb8AhBaqcuAX3dgvEX2E48eBlYFDAWXEJiMlQMbEiiNGoAbhAPIA https://www.typescriptlang.org/play?ts=5.2.0#code/N4KABGDGD2... This link might work when TS 5.2.0 is released.
- Alifatisk 3y agoInteresting, 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]: () => {...} }
- jnspts 3y agoIt'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" };
- singularity2001 3y agoSo 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?
- krona 3y agoSymbol.dispose is just a well-known symbol. Symbols are a fundamental part of the language, not a hack. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#well-known_symbols https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- mostlylurks 3y ago> 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].
- paddw 3y agoIt 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.
- hfkwer 3y agoIt's based on an ECMAScript proposal that is going to be added to mainstream javascript. Typescript is just adding it faster before the browsers.
- WorldMaker 3y agoStage 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).
- nightpool 3y agoBy 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.
- liftsh 3y agoReminds 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.
- 3cats-in-a-coat 3y agoYou'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).
- TazeTSchnitzel 3y agoIn 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.
- nightpool 3y agoThis 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).
- mostlylurks 3y agoThe 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) });
- throwanem 3y agoI 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.
- lolinder 3y agoI 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.
- progx 3y agoNice 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.
- kriz9 3y agoI expected the example to be less code. Not more. Other than making it more familiar for C# devs what value does it add?
- ajanuary 3y agoIt 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.
- kriz9 3y agoUsing 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 https://node-postgres.com/apis/pool#poolquery
- ajanuary 3y agoFrom 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.
- kriz9 3y agoI linked the wrong example. This is the one without having to specifically call release. https://node-postgres.com/apis/pool#poolquery https://node-postgres.com/apis/pool#poolquery
- killthebuddha 3y agoI feel like the toy examples in the article might only make sense to people who already know why they want this feature. The examples don't make it at all obvious to me why I would want this feature. The new version in the examples have more code, more indirection, and more magic (in the sense that it relies directly on a specific property of the runtime). Could anyone here help me understand a more robust example where `try...finally` just won't work (or at least would be patently less readable/etc)?
- hn_throwaway_99 3y agoIt's essentially the exact same thing as the using statement in C#, so if you search for that you should be able to find more info. But as to your question "Could anyone here help me understand a more robust example where `try...finally` just won't work", using is just basically syntactic sugar for try/finally, but I'm very much in favor of syntax that gets rid of lots of verbose boilerplate that can obscure the real purpose of your code.
- robby_w_g 3y agoThe proposal[1] explains several cases where try…finally is verbose or even can result in subtle errors. The upshot for me is that this feature adds RAII to JavaScript. It makes resource management convenient and more maintainable. Seems like a no brainer to me. [1] https://github.com/tc39/proposal-explicit-resource-management#definitions https://github.com/tc39/proposal-explicit-resource-managemen...
- DougBTX 3y ago> The upshot for me is that this feature adds RAII to JavaScript That doesn’t appear to be the case, a resource can be returned, but this is block scoped. It appears to be closer to a using statement in C#.
- TJSomething 3y agoBased on my reading of the spec, you can't actually return a resource, since the disposal semantics need to map exactly to calling [Symbol.dispose] in a finally block at the end of the current block. Also, unlike C#, you can have multiple using statements in the same block, which will be disposed of in LIFO order.
- BiteCode_dev 3y agoSo, like context managers in Python, except you only have `__exit__` and not `__enter__`. What's missing from the article is the semantics when there is an error. In Python, `__exit__` is always called (with the exception as param), but depending of if you return True or False, you reraise or not the exception. Anyway, it's a useful addition. While you can craft an API with this behavior in JS by accepting two callbacks, it's nice to have a standardized way to do it. Plus making it an official feature will encourage the pattern, which nurture the culture for better API.
- bhouston 3y agoInteresting. I sort of did a similar pattern in some of my code (https://github.com/bhouston/threeify/ https://github.com/bhouston/threeify/) but just with existing TS capabilities: export function using<T extends { dispose(): void }>( resource: T, func: (resource: T) => void ) { try { func(resource); } finally { resource.dispose(); } } // usage using(new Resource(), (resource) => { // insert code that uses the resource });
- _old_dude_ 3y ago"using" as specified is too OOP for my taste, i think i prefer "defer" like in Golang. let connection = getConnection() defer connection.dispose() ... being equivalent to let connection = getConnection() try { ... } finally { connection.dispose() } "defer" unlike "using" does not require a specified entry point method, so no [Symbol.dispose] required.
- duped 3y agoYou're pawning complexity off to the caller of `getConnection` with `defer` semantics. The only time `defer` is preferable to RAII is when the release of a resource to its corresponding acquire may have a different set of arguments depending on the context. The example you've given doesn't really seem that compelling. acquireX/releaseX maps nicely to lexical scope and adding a language feature to provide sugar for it is pretty nice, imo/e. Defer doesn't really "get it."
- computomatic 3y agoThe salient point from the parent comment was that “using” assumes the resource is an Object. The core value prop of both “using” or “defer” is that a single scope can manage the lifetime of some resource while child scopes interact with that resource. “Using” assumes that resource is a local object but that’s rarely actually the case - as in the example of a DB connection - it’s just how OOP represents it. A good counter-example would be if I need to manage a resource via an API. Maybe one request creates the resource and another tears it down. (Eg, begin a transaction and then commit it). “Using requires me to implement a new class to represent this resource - that’s OOP at its finest - whereas “defer” allows just doing the thing (and works equally well for objects)
- Timon3 3y agoThere is no need for you to create a new class to represent a resource. Javascript doesn't require objects to be instances of specific classes, so you can just return an object with the dispose method while only using it as a container. Even when using Typescript there is no need to add a new class, a new type declaration is more than sufficient. Just because it's on objects doesn't mean it's OOP.
- localghost3000 3y agoMaybe I am dense, but in the examples given, the code this feature is meant to replace looks much more straight forward and easy to reason about. I get the use case here: automatically manage resources that need to be opened/closed like files etc but the syntax is way too convoluted IMO.
- throwanem 3y agoIt's a new lexical binding form parallel to const and let, and a requirement that any value so bound have a property with a specified name. Where do you see the convolution?
- localghost3000 3y agoI understand what I am looking at. It's just that it just reads a lot less straight forward than the code its looking to replace IMO.
- throwanem 3y agoI get that that's what you're saying, I guess I just don't understand how it reads as more complicated. Different strokes, though.
- pwdisswordfishc 3y agoThis is not TypeScript-specific, it’s a stage-3 ECMA-262 proposal. https://github.com/tc39/proposal-explicit-resource-management/ https://github.com/tc39/proposal-explicit-resource-managemen... It’s a shame the author insisted on it looking like C# and sabotaged attempts to combine disposal with destructuring. Also, there is no move operator without futzing around with DisposableStack. Though that could be introduced later; it’s not a day-zero design issue.
- ignoramous 3y agoTFA mentions as much: TypeScript 5.2 will introduce a new keyword - 'using' - that you can use to dispose of anything with a Symbol.dispose function when it leaves scope. This is based on the TC39 proposal, which recently reached Stage 3, indicating that it is coming to JavaScript.
- paraboul 3y agoAgreed with OP, the title is misleading. Typescript technically doesn't introduce any new runtime feature. This is basically a polyfill for an upcoming Javascript feature.
- franciscop 3y agoNo, I am very familiar with JS and wasn't sure reading the article what bits were the new TC39 proposal and what bits were TS-specific. It seems, from having read it once quickly, that it's just TS adding support early to a feature that will come in JS, similar to how you could add support to async/await in your transpiler before they were commonplace by transforming them into promises back in the day. But that's definitely not a "TS new keyword".
- Dylan16807 3y agoFrom a brief search, it looks like the semantics around destructuring and what exactly gets disposed were causing confusion? What's the not-C#-like method that would have allowed destructuring in a better way?
- hn_throwaway_99 3y ago
- thangngoc89 3y agoSince everyone mentioned about C#, I will contribute the example of the same feature for Python: with open(“file.txt”) as f: f.write(…)
- qprofyeh 3y agoI like the Python version better because it starts a new block, which is more explicit than the using keyword + current block termination.
- WorldMaker 3y agoC# used to require blocks, but as more and more things needed to be disposable you'd often find code bases winding up somewhat unreadable "waterfalls" of blocks, for various reasons. Languages automatically track the scopes for a lot of things, especially in the cases of async/await functions and generator functions which build complex state machines. Automatically tracking the scopes of using statements isn't that much different than tracking let/const through all of a function. Some languages make that explicit, too. (The most common LET in LISP requires explicit "blocks", as an example.)
- crubier 3y agoI thought the same at first, but forcing one additional scope level / layer of indentation for each disposable resource ends up looking a lot like "callback hell", with deeply nested code. I've seen that in Python many times. I like the thoughtfulness of Typescript, not going for the naive solution but trying to see how it will scale.
- throwaway12311 3y ago[flagged]
- colonwqbang 3y ago"Using" -- a poor choice of keyword. It has been used for many years in languages like C++, C# (and now Rust) for namespace manipulation. Using the same keyword to declare an object destructor becomes confusing. For a language like Typescript which borrows so much of its syntax from C/C++ it would be best to either make the same syntax mean the same thing, or instead make up new syntax. Call it "with" or "defer", or something else instead. Also the syntax "Symbol.dispose" to denote a symbol seems overly verbose. Why not use a single prefix character like Lisp/Ruby ":symbol".
- jraph 3y ago"with" was already taken by a misfeature of JavaScript, so… I too thought they were introducing something related to the "using" of C++, but the keyword does not seem too bad, in the context of a code, it's very readable. What would you suggest? As for Symbol, it's a JavaScript feature. I bet it was introduced like this so symbols could be polyfilled, so adopted more easily, without making the lack of support a syntax error - i.e. no need to transpile this. A bit verbose, but bearable, and probably easier to parse for a human than a colon. Too much of such terse syntax rapidly makes the code harder to read and more difficult to look up for a novice, or possibly even for someone experimented..
- BjorksEgo 3y ago"Using" is used for the same purpose in C# tries to keep keyword symmetry with https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/using https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
- winrid 3y agousing obj { do stuff } would have been so much better
- hankchinaski 3y agoSeems similar to Go “defer” keyword
- shortrounddev2 3y agoI just want TS to have some kind of RTTI. It would be really cool if you could generate a list of keys on a type or object using `keyof`. It would make typescript significantly better for backend development so that you could automate input validation rather than having to write some kind of swagger annotation on every single field. Would make generics a lot more useful, too. I know the reasons that they don't do this, but I think without that kind of metaprogramming, typescript and node remain poor choices for backend development
- davedx 3y agoObject.keys?
- shortrounddev2 3y agoDoesn't work on types, only instantiated objects with values for those keys: class Foo { foo: number | undefined; bar: string | undefined; }; console.log(Object.keys(Foo)); // prints [] console.log(Object.keys(new Foo)); // prints []
- stewartmcgown 3y agoI have always used class-validator for this. The custom validation stuff always comes up in backend dev eventually so starting with it has always been the right call for me. This includes small toy projects and entire SaaS backends.
- shortrounddev2 3y agoYeah we use nestjs decoratos as well, but there's limitations, since the decorators run at runtime. For example, we wanted to create a Query DTO for our API. It would be a generic class that allowed you to specify the fields you wanted to return in a query, like: { include: ["Field", "Foo", "Bar"] } You can construct this in typescript like: class Foo { bar: number | undefined; foo: string | undefined; }; class Query<T> { includes: Array<keyof T> | undefined; }; And use the query like: let q = new Query<Foo>; q.includes = ["foo", "baz"]; // fail: "baz" isn't in `keyof Foo`; But since types are erased at runtime, and typescript classes don't work like ES6 classes (which would actually make a hack to support this kind of runtime validation available), you can't validate through an enum on the field, like: class Query<T> { @ApiProperty({ enum: keyof T // meaningless at runtime, won't compile }); includes: Array<keyof T> | undefined; } And since Object.keys doesn't work, you can't validate the keys for the object at runtime unless you ditch the generic and just write out the same boilerplate code over and over again with different values for every type you want to be able to query. Typescript was obviously designed to run in a browser, where it originates data, and not on the backend where it receives data, since input validation is such a chore on the backend. I don't even want a runtime call for `keyof T` like `typeof`; I just want typescript to compile `keyof T` into an expanded array of strings. I would even settle for a C/C++ style preprocessor macro support. There have been times when I wish I could just log the filename of a function in an error using a preprocessor like in C/C++: cout << "Error in file: " << __FILE__ << endl;
- maxfurman 3y agoI've been writing some C# lately and `using` has been very nice to use with file handles, buffers, etc., that might need to be cleaned up. But they only work nicely in C# because the C# stdlib has properly implemented `Dispose()` for File, StreamReader, et. al. Whenever I'm dealing with library types I have no idea if I'm supposed to dispose of them or not. We've got a similar problem here with JS where the node team has to implement `[Symbol.dispose]` in all the relevant classes (ugh) and who knows when that will land. And until then the runtime has to clean up disposable resources without the help of `using`. So what's the point?
- mattmanser 3y agoBecause at some point it will be better. Typescript is strongly typed too so I assume it won't let you use using on things that don't support it.
- mmis1000 3y agoThese arguments can be also applied to promise and await. And they almost ultimately won these days. Sure, you need to implement the then function or whatever properly or it won't play nicely with code other wrote. But once enough people adopted it, it's almost no way to go back. If you are a big library author and you don't bother to, probably other will just write a wrapper(request-promise for example) or make a clone with that buildin(there are so many and it's pointless to count). It's FOSS anyway.
- maxfurman 3y agoYou can await values that aren't already async in JS. IIRC the runtime turns it into the equivalent of `await Promise.resolve(x)`.
- Jochim 3y agoMy experience in C# has been that almost all library types that require resources to be released will implement the same IDisposable interface provided by the System namespace. I'm sure there are examples of libraries that don't do this but I can't think of any off the top of my head and I'd assume that they're fairly rare.
- seniorsassycat 3y agoAny other good articles on this, other than the proposal spec? 1. Is dispose run when the object is Garbage, or when the nearest scope exits? 2. Is `using` const or let? 3. Is dispose looked up on declaration or before call? 4. Are chained resources disposed, or only the last? 5. What happens if the using resource is not disposable?
- bakkoting 3y ago1. Scope exit. 2. Const. 3. On declaration. 4. I don't know what "chained resource" means. If you mean multiple declarations within the same scope, they all get disposed, in LIFO order (even if disposing one of them throws). 5. An error when the `using` statement runs. (Unless the RHS itself is null, rather than an object without a `Symbol.dispose` method; this lets you have conditional declarations.)
- seniorsassycat 3y ago4 I mean `using x = a(b(), c())` if all functions return a disposable value will all three be disposed, or only the value returned by a and assigned to x?
- bakkoting 3y agoOnly the value assigned to x.
- seniorsassycat 3y ago5 is an interesting choice, what is the downside to allowing `using` on non-disposable values, treating it like a no op disposer? Otherwise a function that takes a maybe-disposable object parameter has to check if the symbol is defined, or the caller has to wrap the non disposable object
- bakkoting 3y agoBecause it's usually an error, and the benefit of catching the error cases outweighs the cost of losing that flexibility.
- disintegrator 3y agoI was really excited about this feature because I recently tweeted about wanting something like `(*testing.T).Cleanup` from Go [1]. I think this is achievable now across test frameworks because of this language/runtime addition! I scribbled something together and I'm looking forward to testing it out when TypeScript 5.2 is out: https://gist.github.com/disintegrator/35b6a6a87dbad54fada430d8f8efa905 https://gist.github.com/disintegrator/35b6a6a87dbad54fada430... [1]: https://pkg.go.dev/testing#T.Cleanup https://pkg.go.dev/testing#T.Cleanup
- moomoo11 3y agoI feel dumb I don’t understand the page at all. What are some use cases for this?
- moomoo11 3y agonvm I get it now lol
- dmix 3y agoAre there other JS interfaces that use the global symbol pattern? ie [Symbol.dispose] or is this some new approach? I get it but haven't really seen that sort of thing before.
- dvlsg 3y agoYeah, there's a number of cases that use the Symbol pattern. They're all relatively new, but that's at least in part due to Symbols themselves being relatively new. Symbol.iterator comes to mind as a decent example.
- Me1000 3y agoYou can find a more complete list of "well-known symbols" here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- dmix 3y agoThank you!
- deleted 3y ago[deleted]
- nojvek 3y agoTypescript usually doesn’t implement features like this unless they are part of the core JS language. I believe this is a JS feature.
- AtNightWeCode 3y agoJust go ahead and add the same poor design as in C#. Using statements is probably the one single construction in C# that caused most bugs. I worked with that crap for nearly 20 years. I even had a bug in production today that most likely has to do with these "hidden" scopes. Why on earth does someone think it is a good idea to finish some work at some arbitrary time instead of what is expected when reading the code top down. Puzzling.
- d--b 3y agoOof, this is Microsoft-y! Next is COM support?
- pwdisswordfishc 3y agoYes, and an ActiveXObject constructor.
- huksley 3y agoWhy it uses [Symbol.dispose]: () => {} instead of just dispose: () => {} Would somebody actually add "using" without checking type?
- deleted 3y ago[deleted]
- forty 3y agoThe problem with structural/duck typing is that words can mean several things. For example `.run`ning your duck is probably very different from `.run`ning your Thread. I think the Symbol is a way to signal "this is the dispose function whose contract is defined in the JS `using` spec and not some random function that happens to be named that way".
- raydiatian 3y agoThis shit pisses me off. This goes against the language design anti goals. And there’s way more stuff from other languages that is far more valuable than using.
- ufo 3y agoReminds me of <close> from Lua 5.4
- no_wizard 3y agoMicrosoft really wants JS to be C# doesn’t it? All their supported proposals is about reflection, attributes (ie decorators) and in this case fine grained GC management (disposable resources). I know a lot of their code embraces ES6 classes and such. I wonder if they will put forth a new proposal for dependency injection next, at this rate. All this is to say, I don’t know how I feel about it
- balls187 3y agoReflection at run-time will be interesting. I believe that additional capabilities squeezed out of javascript if programmers can utilize type information at runtime.
- oaiey 3y agoWell DI is a standard lib or even Framework thingy. Have not seen anyone except Google (PWA/Chromebook) advancing the standard lib in any structural way. DI hast no space in the Standard lib. It is Framework space. It belongs into react, svelte or Angular. But not into a standard lib.
- bakkoting 3y ago> I wonder if they will put forth a new proposal for dependency injection next, at this rate. Yes, there is a new-ish proposal [1] which is motivated in large part by DI. (Full disclosure: I'm on the committee and don't really like DI or this proposal. But we'll see how it goes.) [1] https://github.com/tc39/proposal-class-method-parameter-decorators https://github.com/tc39/proposal-class-method-parameter-deco...
- no_wizard 3y agoI also want to point out, that instead of fixing the `with` operator[0] which does the same thing (scope management), they introduced a Symbol. Which is fine, for what it is, however it does limit implementation a little bit, using something like a proxy is a bit ambiguous for example. [0]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- the-alchemist 3y agoFor those more familiar with Java, this looks like this is very similar to Java's try-with-resources: static String readFirstLineFromFile(String path) throws IOException { try (FileReader fr = new FileReader(path); BufferedReader br = new BufferedReader(fr)) { return br.readLine(); } } Instead of defining a function with Symbol.dispose, you have to implement the java.io.Closeable interface. Java has had this since, cough cough 2014.
- mstade 3y agoWhy is the `using` keyword necessary, isn't the existence of a `[Symbol.dispose]` property enough? I.e. always call `[Symbol.dispose]()` when exiting the scope. Maybe have a keyword to not dispose the resource. Seems the current design makes it very easy to forget adding the `using` keyword, leaving resources dangling.
- rbalicki 3y agoIn my opinion, this is a flawed proposal. The disposer should not be a method on the object, because it means it cannot pass around the resource without the risk that the recipient of the resource disposes it themselves. A safer alternative is available- [item, disposer]. See this issue: https://github.com/tc39/proposal-explicit-resource-management/issues/160 https://github.com/tc39/proposal-explicit-resource-managemen...
- hn_throwaway_99 3y agoMeh, I don't think the more confusing interface defined in that issue warrants the added complexity. using should only be used for local variables, and the example you give is fundamentally complicated by who should be responsible for disposing of the object: function inner(defaultResource) { const resource = defaultResource ?? getResource(); using resource; } That is, if a defaultResource is passed in, the caller should be responsible for disposing it, while if it wasn't passed in, the inner function should be responsible for disposing it. IMO cases like this are better handled with linting rules that forbid use of using on anything but an object directly returned by a function on that line.
- deleted 3y ago[deleted]
- lolinder 3y agoIn another language I think your proposal could be quite reasonable, but it requires using patterns that aren't common in JavaScript, which would complicate the language more than the alternate proposal. The biggest problem I see is that to use the feature in a class you'd be forced to define a static factory method to produce the object-disposer tuple. To be effective, a static factory needs to be paired with a private constructor, but JS has no first-class support for those, which means you have to go to contortions with exceptions to make sure people don't accidentally instantiate a non-disposable instance of the class. The existing proposal uses a well-establish way to interact with language features—the well-known symbol [0]. It's compatible with any way of defining an object, and doesn't require using any particular pattern that may be unnatural for a subset of the community. [0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#well-known_symbols https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- jshen 3y agoDoes anyone else see this and think, "async programming is much worse than non-async programming"? Compare this to Go code, and I'll take the equivalent Go code any day of the week.
- circuit10 3y agoSo it’s like “with” in Python
- altano 3y agoMy profile has said "I wish JavaScript had RAII" for almost a decade. I'm glad I can finally change it to "I'm glad JavaScript has RAII"
- guluarte 3y agojust make a c# to js transpiler at this point...
- Sophistifunk 3y agoHow does this deal with escape analysis etc? I can't see how to implement this without vm changes or extreme code transforms which would be antithetical to the spirit of typescript.
- sethaurus 3y agoThere’s no escape analysis at all; it’s purely lexical.
- Sophistifunk 3y agoYeah, I figured as much after taking the time to read the TC doc. Seems like sugar that doesn't really add much. I was excited when I first saw the headline.
- kazinator 3y agoI added something like this to TXR Lisp in 2015. with-objects binds variables to objects, like let. When it terminates, the GC finalizers of the objects (if they have any) are invoked. 1> (with-objects ((x [finalize (list 1 2 3) (lambda (obj) (format t "bye-bye ~s\n" obj))])) (format t "inside with-objects: x is ~s\n" x)) inside with-objects: x is (1 2 3) bye-bye (1 2 3) Mainly this is meant to be used with OOP structs, where you can declare finalizers. 1> (defstruct widget () (:fini (me) (put-line `@me's finalizer`))) #<struct-type widget> 2> (with-objects ((w (new widget))) (put-line `have @w`)) have #S(widget) #S(widget)'s finalizer t It provides a kind of RAII that can be useful from time to time.
- wruza 3y agoWhile it seems fine and useful, there’s still an aftertaste of c++ness. That’s exactly how `A a;` becomes a statement that does everything and your cat without you noticing.
- kapv89 3y agoI wish ts/js hackers just add proper threads (or green threads) with shared memory parallelism to the language instead of adding these gimmick-y features
- angryGhost 3y agois this similar to java's try with resource?
- dingi 3y agoLet's hope TS does not become a bloated mess having every feature but kitchen sink like C#
- Vanit 3y agoInteresting, seems this implies you could implement enumerable alternative to WeakMaps?