8 ms·
Finally a nice async/io interface for rust, always felt that it was a big missing piece, couple of questions for peps familiar with async in other languages :
by soulbadguy 10y ago
Finally a nice async/io interface for rust, always felt that it was a big missing piece, couple of questions for peps familiar with async in other languages :
1 - Isn't the state machine approach the same as C#/.net async/await is using ? But the with the added convenience of the syntactic sugar ?
2 - no allocation : , does'nt the lambda closure need to be allocated somewhere ?
3 - I would have love some comparison (booth performance wise and on theory) with C++ up comming coroutine work, from my understand the C++ approach is even more efficient in term of context switching and have the advantage of even less allocation.
- steveklabnik 10y ago1. I am not sure exactly how async/await is implemented, but I believe it is very similar. Some people are also working on implementing similar sugar in Rust, but it's not done yet. 2. Closures are on the stack, not the heap, by default, in Rust. If you don't see a Box, they're not heap allocated. 3. I agree!
- moosingin3space 10y agoStack-allocated closures are one of my favorite features of Rust. They make it easy to write functional-style code that performs well.
- indeyets 10y ago(1) here's how it's done in JS: https://tc39.github.io/ecmascript-asyncawait/#desugaring https://tc39.github.io/ecmascript-asyncawait/#desugaring
- pcwalton 10y ago> Isn't the state machine approach the same as C#/.net async/await is using ? But the with the added convenience of the syntactic sugar ? Similar in principle, but the implementation is different. Tasks in C# are more of an OO style instead of a FP style where they turn into an enum (sum type, if you want to get theoretical). > 2 - no allocation : , does'nt the lambda closure need to be allocated somewhere ? No, not in Rust (or in C++). > from my understand the C++ approach is even more efficient in term of context switching and have the advantage of even less allocation. No, you're asserting this without understanding Rust. It's not possible to get less than zero allocation.
- soulbadguy 10y agoYes , i only have a simple understanding of rust memory model . So what happened when/if the future escapes/outlives the current stack/context ?
- Rusky 10y agoThis is handled by the borrow checker, the same as anything else that might hold references to stack data - you get a compiler error and then can decide how to resolve the conflict, either by extending the lifetime of the referent (perhaps by boxing, or creating it earlier in the call stack), or by shrinking the lifetime of the future.
- steveklabnik 10y agoFrom the post > There are essentially no imposed synchronization costs; if you want > to associate data that lives on your event loop and access it in a > single-threaded way from futures, we give you the tools to do so.
- dbaupp 10y agoIt's an error if that would be unsafe, or else it compiles fine: the future compiles down to an enum/struct that stores everything inline and it can be returned/move up the stack/stored in global memory the same way that aggregate values returned without allocation (etc.) in C or C++.
- aturon 10y agoTo be clear, the issue here isn't so much stack vs heap, as much as how many heap allocations are happening. In practice, you build up a really big combined future on the stack, which has all of the space needed for any state in its state machine, and then when the future is fired off (in a task -- one per connection), that entire thing is moved into the heap in one shot. Thus, generally, you do one big allocation up front per connection -- exactly the same as you'd do when writing state machines by hand. The follow up posts will go into much more detail on this point :)
- 10y ago
- Rusky 10y agoIn Rust, closures don't need to be boxed, because they're just bytes like everything else. In this case, they can be stored by-value as a generic type in the future itself, which can be stored on the stack. The one time that you do tend to see closures get boxed is when returning them, because you can't write out their type. In the future you'll be able to get around that by using the `impl Trait` syntax mentioned in the post (https://github.com/rust-lang/rfcs/pull/1522 https://github.com/rust-lang/rfcs/pull/1522).
- masklinn 10y ago> 2 - no allocation : , does'nt the lambda closure need to be allocated somewhere ? I expect it is embedded in the state machine structure. In Rust (and C++) lambdas/closures are "value types".
- astn 10y agoIn C++ lambdas have limited size (about 3 machine words). If it's context is larger than that, the data may be allocated on heap (if compiler can't optimize that allocation out).
- soulbadguy 10y agoI though in c++ the lambda get created on the stack like any other object.
- astn 10y agoThey do, but to actually use it, to pass it anywhere, you have to either wrap it in some other object (std::function, as comex pointed out) or use a template.
- comex 10y agoWhat you're talking about is std::function, a wrapper around raw lambda objects that performs dynamic dispatch. Since any lambda can be wrapped in std::function yet std::function, like all C++ types, must have a fixed size, it uses the heap as you say. If you stick to the raw lambdas, however, the compiler knows the size and doesn't need to use the heap. Only problem is that their type can't be named. In C++, you can work around this with auto. Without getting too much into it, Rust has a similar distinction between raw lambdas and Box<Fn(...)>. This library uses the former.
- gpderetta 10y agoC++ programmer here; re 3: the C++ coroutine proposals (there are multiple) are completely orthogonal to futures. The closest things in the standard to these rust futures, is of course std::future, which currently is very limited as it even lacks chaining and composition. The Concurrency TR (which eventually will be added to the standard, but sadly not for C++17) does provide these features (and more); multiple implementations have been are available for some time (boost, HPX, Folly, and some compiler vendors). The issues with std::future is that is far from zero cost, as it requires allocation (the control block is dynamically allocated), type erasure (the callback type is not part of the future) and synchronisation (as the promise can be fulfilled from another thread). Efficient implementations of std::future are possible but will never be as fast as what is shown in this rust library. On the other hand the SeaStar C++ library (I'm in no way involved, just a fan) already provides pretty much the same zero cost futures with a very similar implementation as it doesn't try to follow the standard design.
- soulbadguy 10y agoThanks your answer clarifies a lot. So my question was , as a mechanism to implement async operation how does C++ coroutine compares to this rust library. From my understanding the C++ committee is pushing for the introduction of coroutine specifically because any library base approach would have the limitation you mentioned. Is that a correct ? You seems familiar with the C++ committee, do you know a good way to track a feature one is interested in ? I kinda lost track of the coroutine proposal after the 4 revision or Gor paper. By the beside Visual studio, any other compiler has a working implementation of the current draft ?
- gpderetta 10y agoCoroutines would still run on top of std::future, it would be Just syntactic sugar to avoid callback hell via then chaining. In same case this might save some allocation as the callback would be a plain pointer to the control block, but that's about it. I'm not part of the committee nor I attend meetings, but I follow the papers and the public online discussions.