4 ms·
A couple of random thoughts: 1. If it's like Javascript, async and await basically boil down to converting to and from Promises. There's no reason to have to t
by zellyn 7y ago
A couple of random thoughts:
1. If it's like Javascript, async and await basically boil down to converting to and from Promises. There's no reason to have to transitively perpetuate `async` annotations: you can just deal directly with a completely normal Promise object instead. (But you soon find you like `async` better :-))
2. The “caller decides” model is actually considered good practice in Go: expose synchronous primitives, and let callers compose them with Goroutines if they want to.
- steveklabnik 7y ago> If it's like Javascript It is and it isn't. That is, > async and await basically boil down to converting to and from Promises In Rust, they basically boil down to converting to and from Futures. However, the major difference is that while in JavaScript, promises start running upon creation, in Rust, a Future doesn't start executing until you submit it to an executor. This has many implications...
- DSingularity 7y agoWhat’s the difference? Futures and promises come in pairs, right?
- steveklabnik 7y agoI'm not 100% sure what "come in pairs" means, exactly. Here's a sort of quick rundown. A Promise is in one of these states: pending, fulfilled, or rejected. When you create a promise, you set a callback function that is the "body" of the promise, and can transition it into the fulfilled or rejected state by calling either of two callbacks passed into it. When you create a Promise, it's in the pending state, but the runtime sets it up to be executed, and when a tick occurs, it will start executing automatically. In Rust, a Future is something with a poll method that returns either Pending or the final value. If you want JavaScript's behavior around failure, you make the final value a Result, it's not built-in. In Rust, unlike in JavaScript, a Future is inherently inert. Nothing happens until you actively call the poll method of said future. This means that, unlike in JavaScript, you have to either call poll yourself (not recommended) or pass it to something that will. We colloquially call those things "executors." This can lead to surprising behavior if you're used to the JavaScript model; many JS programmers coming to Rust will create a future, and then be surprised when their code compiles but doesn't work. It's usually that they forgot to submit things to an executor. In both cases, you can chain these things together, which is how they're usually used to produce some bigger computation. Okay, so from an interface perspective, that's the difference. But the difference goes a bit deeper than that. For example, since Rust futures are nested when you chain them, and because the language is always AOT compiled, a chain of Futures compiles down to a state machine with a perfectly sized stack. Whereas a Promise has a separate object for each step in the chain, with all of the excess allocations and such that implies. I am not 100% sure what the state of the art is in optimizing promises, but in Rust, you always get the most optimized thing, since it builds off of everything else in the language. Okay that's all I've got for now, this is already too long. This is a complex topic...
- anp 7y ago> If you want JavaScript's behavior around failure, you make the final value a Result, it's not built-in. Fun bit history: the first pass at Futures did return an error type explicitly: https://docs.rs/futures/0.1.28/futures/future/trait.Future.html#tymethod.poll https://docs.rs/futures/0.1.28/futures/future/trait.Future.h.... IIUC the idea was that Futures were for performing I/O which is effectively always fallible. I assume that it's an easy design decision to make looking at all of the other languages with similar designs. I'm very glad Rust has nicely orthogonal "deferred"/"fallible" types now.
- kowdermeister 7y ago> promises start running upon creation... > ...until you submit it to an executor That's not much different. Don't call the function then which returns that promise :) Or you can write: sendRequest(thingThatReturnsPromise);
- steveklabnik 7y agoSure, it’s easy to remember once you know it. It’s a very common foot gun for the JS folks coming over, and can also affect how you structure your code because of it. Code that runs in parallel in JS will be sequential when naively translated. I’m in the process of writing a blog post about this...
- kowdermeister 7y agoThanks btw for the warning, I'm learning Rust slowly by going through the awesome Rust book.
- zellyn 7y agoThat makes sense. I'm glad you're standardizing all this early, unlike Python which created an entire Twisted parallel universe for everything and only standardized later.
- steveklabnik 7y agoYeah, we were keenly aware of these splits happening and so worked really early to figure out how to not split the ecosystem. It’s been a long hard road...
- fapjacks 7y agoAny chance you've written about specifically trying to foresee and prevent a split (Rusty? Rusted?) ecosystem? As an armchair PL, amateur SIGPLAN kind of person, I would be very interested to hear about your experiences. I couldn't find anything after a cursory search, though I did see a couple of posts where you briefly but explicitly mention your choices.
- steveklabnik 7y agoIt’s mostly “traits are how interop is done, if there’s an ecosystem of multiple competing things, try to pull out common traits for folks.”
- deleted 7y ago[deleted]
- bernawil 7y agoIn Javascript you can make anything into a lazily evaluated expression by wrapping it into a function (sometimes called a "thunk"). // eager new Promise(resolve => resolve(foo)) // lazy const bar = () => new Promise(resolve => resolve(foo)) An executor would be something that takes n functions that return Promises and calls them.
- steveklabnik 7y agoSure, we do this in Rust too. I’m not sure what benefit this would bring you in JS.
- bernawil 7y agoIt's most useful with anything sending multiple requests. Most often you have all the data to perform all requests beforehand, but you can't just fetch (or whatever xmlHttpRequest abstraction you like) to send all requests at once eagerly because of: performance in the browser (running something like 10 simultaneous ajax requests in a single tab often can really lag it) and rate limiting in the back end. So it's better to prepare all requests wrapped in a thunk and then you can execute them at will. You can really optimize as you like, like running some in parallel, secuentially or in batches. Of course this is for really custom cases, more often than not you are best served by something like Promise.map in bluebird which gives you a concurrency option. (http://bluebirdjs.com/docs/api/promise.map.html http://bluebirdjs.com/docs/api/promise.map.html).
- steveklabnik 7y agoGotcha, thanks!