3 ms·
I really like cancellation in Rust, and I don't mind that it's so forceful. Maybe it's because I've worked with Node.js which is all the way at the other end of
by pornel 5y ago
I really like cancellation in Rust, and I don't mind that it's so forceful. Maybe it's because I've worked with Node.js which is all the way at the other end of the spectrum with no way of aborting promises externally. In Node if you want to abort an async operation, you need to thread a DIY signal/flag/callback throughout your entire program, or you'll be left with orphaned tasks still running. Compared to that, Rust's futures that just immediately stop when you abort them are a luxury.
I've written quite a bit of async Rust code, and I haven't hit the problem described in the blog post. I think there are a couple of reasons:
• Rust already has programming patterns (like Drop guards) that avoid leaving the program in an inconsistent state in case of panics, and this solves the problem for aborted futures too. If your tasks owns a TCP stream, then it will always close the stream when it's aborted.
• Aborting and error handling are usually tied together, e.g. `timeout(copy()).await?` aborts a future, but turns that into an error. Especially for network-related tasks it's natural to treat timeouts as yet another case of an I/O error. Each end needs to handle suddenly closed connections anyway, regardless whether they're closed by Rust or a network error along the way.
• Rust uses layered abstractions for async code, e.g. you have request/response servers or streams for protocols. This way you can't leave the network protocol in an inconsistent state, even if your request handling code is aborted. If you were writing to a multiplexed HTTP/2 stream, then the underlying protocol handler will send an end-of-stream packet for you when your higher-level response stream is dropped.
- wging 5y agoOne current gap is that async drops aren't currently a thing - you cannot do any async work to clean up a resource. I imagine Niko will point that out in the next post. See also https://boats.gitlab.io/blog/post/poll-drop/ https://boats.gitlab.io/blog/post/poll-drop/
- ithkuil 5y agoI toyed a little bit with an approach to async drop; let me know what do you think https://crates.io/crates/arcy https://crates.io/crates/arcy
- leshow 5y agoNot OP, async drop isn't a thing, but you can still have a drop guard in an async function. It's not as nice as having async drop obviously, because you can't run any await-able code inside Drop, but it still works for some situations: async fn foo() { let guard = Guard; // async stuff } impl Drop for Guard { fn drop(&mut self) { // do cleanup } }
- pornel 5y agoThis is indeed a big gap for interacting with non-Rust async APIs where you can't dictate memory management strategy. However, within Rust, it rarely comes up. Probably because native Rust libraries/interfaces have no choice but design around it. If you really have to finish some work asynchronously on drop, you can spawn another task from your drop function. But most of the time I've found it's not even necessary, because you can restructure the code to separate the "abortable" part from the "must finish" part (e.g. some network protocol serializer must always terminate a message it writes, but generation of the message itself is typically done elsewhere, so you can set it up so that abort of the message-generating future doesn't abort the protocol-framing future).