LWN: Comments on "Canceling asynchronous Rust" https://lwn.net/Articles/1036924/ This is a special feed containing comments posted to the individual LWN article titled "Canceling asynchronous Rust". en-us Fri, 17 Oct 2025 04:51:34 +0000 Fri, 17 Oct 2025 04:51:34 +0000 https://www.rssboard.org/rss-specification lwn@lwn.net Error in code sample? https://lwn.net/Articles/1040106/ https://lwn.net/Articles/1040106/ riking <div class="FormattedComment"> The crux of the example is that the `timeout` function is invoking cancellation in a way that the code author didn't notice.<br> </div> Tue, 30 Sep 2025 00:31:21 +0000 Error in code sample? https://lwn.net/Articles/1039865/ https://lwn.net/Articles/1039865/ excors <div class="FormattedComment"> I think the Ok(Err) case doesn't count as cancellation, it's just normal synchronous error handling. Cancellation is specifically when a Future is dropped before it has completed (i.e. before its `poll` function has returned Poll::Ready(_)). In this case tx.reserve() constructed the Future, and its `poll` has returned Poll::Ready(Err(_)), so the Future has completed and there is no cancellation.<br> <p> In contrast, `timeout` constructs a new Future that wraps the original Future, and (on timeout) the outer Future completes but the inner Future is dropped (cancelled). And any resources owned by the inner Future (e.g. `msg`, in the original `timeout(tx.send(msg))` example) are dropped too, which is the issue being solved by the later example.<br> <p> In practice the code should have some better error handling for Ok(Err) that isn't simply `return;`, but maybe e.g. it's going to reset the whole channel and the old message is redundant so it's okay to drop it; that will depend on context and is outside the scope of this example.<br> <p> I think in general you shouldn't move the next_message() call in between reserve() and send(), because next_message() might be expensive and perhaps async itself; you might hold the reservation for a very long time, wasting the channel's capacity. You'd need a different way to handle `msg` in the error case, if not simply dropping it.<br> </div> Sun, 28 Sep 2025 11:32:49 +0000 Error in code sample? https://lwn.net/Articles/1039861/ https://lwn.net/Articles/1039861/ Nikratio <div class="FormattedComment"> Ah, you're right, got to work on my reading comprehension :-).<br> <p> I got "timeout" mixed up with "cancellation", since the previous paragraph (and the article overall) is about cancel-correctness. The code handles timeouts correctly, but still drops the message on cancellation. <br> <p> I think a version that handles both wouldn't be much harder, so I'm surprised that the sample didn't look something like this:<br> <p> loop {<br> match timeout(Duration::from_secs(5), tx.reserve()).await {<br> Ok(Ok(permit)) =&gt; { <br> msg = next_message();<br> permit.send(); <br> }<br> Ok(Err(_)) =&gt; return,<br> Err(_) =&gt; println!("No space for 5 seconds"),<br> }<br> }<br> <p> <p> <p> </div> Sun, 28 Sep 2025 09:53:18 +0000 Rust syntax is only getting worse https://lwn.net/Articles/1039837/ https://lwn.net/Articles/1039837/ NYKevin <p> Re composition with Result: Honestly, I think this concern is exaggerated. Option and Result each provide conversion methods into the other, and it would not be unreasonably difficult to do the same with a custom enum. Once you have a conversion method, composition is straightforward enough. <p> As for general flexibility, this seems like a place where Rust's return-type dispatch might be useful. Here's how it might be spelled: <pre> use std::convert::Infallible; // TODO: Replace with ! when it is stabilized in some future version of Rust. use std::time::Duration; use tokio::time::error::Elapsed; pub enum Timeout&lt;V, E&gt;{ Ok(V), Err(E), Timeout(Elapsed), } // const wrap needed so that Result&lt;V, Infallible&gt; and V don't // have overlapping impls. Ugly hack, but it should work. pub trait IntoTimeout&lt;V, E, const wrap: bool&gt;{ fn into_timeout(self) -&gt; Timeout&lt;V, E&gt;; } impl&lt;V, E&gt; IntoTimeout&lt;V, E, false&gt; for Result&lt;V, E&gt;{ fn into_timeout(self) -&gt; Timeout&lt;V, E&gt;{ match self { Ok(v) =&gt; Timeout::Ok(v), Err(e) =&gt; Timeout::Err(e), } } } impl&lt;T&gt; IntoTimeout&lt;T, (), false&gt; for Option&lt;T&gt;{ fn into_timeout(self) -&gt; Timeout&lt;T, ()&gt;{ match self { Some(t) =&gt; Timeout::Ok(t), None =&gt; Timeout::Err(()), } } } impl&lt;T&gt; IntoTimeout&lt;T, Infallible, true&gt; for T{ fn into_timeout(self) -&gt; Timeout&lt;T, Infallible&gt;{ Timeout::Ok(self) } } pub fn timeout&lt;F, T, V, E, const wrap: bool&gt;(d: Duration, fut: F) -&gt; impl Future&lt;Output=Timeout&lt;V, E&gt;&gt; where F: IntoFuture&lt;Output=T&gt;, T: IntoTimeout&lt;V, E, wrap&gt; { // Ugly hack to work around ! not implementing Future. // But this is just placeholder code anyway. std::future::ready(todo!()) } </pre> <p> Clients can implement IntoTimeout if they want to return some exotic thing and convert it into a timeout result. Otherwise, the impls shown above should cover all reasonable cases. <p> Minor caveat: The real Timeout type is itself a Future, which timeout() returns directly. I did not want to bother actually showing how to implement a Future since it is irrelevant, so I repurposed the name Timeout for the final result type and hid the Future's name behind return-position impl trait. In a proper API, it is usually preferable to give a name to the type you return rather than hiding it in this way, at least in most cases. Sat, 27 Sep 2025 18:10:04 +0000 Error in code sample? https://lwn.net/Articles/1039836/ https://lwn.net/Articles/1039836/ excors <div class="FormattedComment"> I don't see the problem there: On timeout it will run the `println!()` arm, then it will go around the inner `loop` and run the `match` to try reserving a slot again, without touching `msg`. The `Ok(Ok(_))` arm passes ownership to `send`, so only the `Ok(Err(_)) =&gt; return` arm (for non-timeout errors) will drop `msg`.<br> </div> Sat, 27 Sep 2025 16:34:24 +0000 Error in code sample? https://lwn.net/Articles/1039789/ https://lwn.net/Articles/1039789/ Nikratio <div class="FormattedComment"> From the article:<br> <p> <span class="QuotedText">&gt; This code won't drop msg if a timeout occurs:</span><br> <span class="QuotedText">&gt; </span><br> <span class="QuotedText">&gt; loop {</span><br> <span class="QuotedText">&gt; let msg = next_message();</span><br> <span class="QuotedText">&gt; loop {</span><br> <span class="QuotedText">&gt; match timeout(Duration::from_secs(5), tx.reserve()).await {</span><br> <p> I think that's wrong, this code will drop `msg` on timeout just like the previous example. Not sure if that's a mistake in the article or the slides?<br> </div> Sat, 27 Sep 2025 10:14:28 +0000 Losing Data? Async specific? https://lwn.net/Articles/1039663/ https://lwn.net/Articles/1039663/ taladar <div class="FormattedComment"> Some similar APIs also just return the owned value to you as part of the timeout.<br> </div> Fri, 26 Sep 2025 07:55:03 +0000 Discarding return value, and ignoring return type https://lwn.net/Articles/1039629/ https://lwn.net/Articles/1039629/ intgr <div class="FormattedComment"> I think in that case writing `drop(create_future())` would make the intention clearer than assigning to _<br> </div> Thu, 25 Sep 2025 21:26:40 +0000 Rust syntax is only getting worse https://lwn.net/Articles/1039540/ https://lwn.net/Articles/1039540/ fishface60 <div class="FormattedComment"> <span class="QuotedText">&gt; Ok(Ok(permit)) and Ok(Err(_)) have got to be some of the worst notations I've ever seen</span><br> <p> It's an abstraction designed to give you the option of deciding "actually, any timeout is an error and I want to bail out immediately" using the ? operator.<br> <p> A possible alternative formulation would be<br> <p> Ok(permit) =&gt; ...<br> Err(TimeoutError::Timeout(...)) =&gt; ...<br> Err(TimeoutError::Other(...)) =&gt; ...<br> <p> by the timeout function extending the Error value of the Result with a new variant, though since TimeoutError would be defined like this<br> <p> enum TimeoutError&lt;T, E&gt; {<br> Timeout(T),<br> Other(E),<br> }<br> <p> and that's logically equivalent to Result there's benefits to reusing it instead of defining an extra type<br> <p> timeout() could define its own return type as<br> <p> enum TimeoutResult&lt;T, E&gt; {<br> Timeout(TimeoutError),<br> Ok(T),<br> Err(E),<br> }<br> <p> and then you could match on it more simply as<br> <p> TimeoutResult::Ok(permit) =&gt; ...<br> TimeoutResult::Timeout(...)) =&gt; ...<br> TimeoutResult::Err(...)) =&gt; ...<br> <p> but in both cases you're adding extra cognitive overhead for new types and losing the helper functions that are part of Result,<br> and you have to narrow the set of operations you call timeout on to things that return a Result.<br> <p> Ok(Ok(...)) matching isn't an inherent problem in rust, it's a problem that developer time is finite and adding a new type to express the result of a timeout costs more than just wrapping your Results.<br> </div> Thu, 25 Sep 2025 15:40:08 +0000 Discarding return value, and ignoring return type https://lwn.net/Articles/1039478/ https://lwn.net/Articles/1039478/ Brezak There's the clippy lint <a href="https://rust-lang.github.io/rust-clippy/stable/index.html#let_underscore_future">clippy::let_underscore_future</a>, that stops you if you discard a future without awaiting it. It's even warn by default. Thu, 25 Sep 2025 13:51:00 +0000 Losing Data? Async specific? https://lwn.net/Articles/1039457/ https://lwn.net/Articles/1039457/ kpfleming <div class="FormattedComment"> <span class="QuotedText">&gt; If a sending a message times out or is cancelled, isn't it expected that it is "lost" without some retry or other logic? How is this an async specific problem?</span><br> <p> Yes, but the example code doesn't allow retry (nor does your blocking example), because the message was already consumed by passing it as an argument to the send() function. Of course the send() function could be changed to accept an immutable reference instead, but then that makes the code using the function more complex because it must ensure that the message gets dropped in the correct situations.<br> </div> Thu, 25 Sep 2025 11:27:16 +0000 Discarding return value, and ignoring return type https://lwn.net/Articles/1039456/ https://lwn.net/Articles/1039456/ kpfleming <div class="FormattedComment"> That's really useful and news to me, thanks for posting it!<br> </div> Thu, 25 Sep 2025 11:22:26 +0000 Losing Data? Async specific? https://lwn.net/Articles/1039442/ https://lwn.net/Articles/1039442/ farnz The root of the problem is that cancellation can happen unintentionally, and there isn't an exact blocking equivalent. You write code that looks "normal", but because you drop a future without polling it to completion (either via <tt>.await</tt>, or via more complex means), things get cancelled when you didn't expect them to be cancelled. <p>It's the equivalent of terminating a process via <tt>kill</tt>, only being done at scope ends; if your code is written to cope with that ("cancel-safe" for Rust futures), nothing bad happens. If your code has done something like "take a work item from a distributed work queue", and expects to push that work item back onto the queue if it can't finish it, you get surprising behaviour. Thu, 25 Sep 2025 09:50:54 +0000 Losing Data? Async specific? https://lwn.net/Articles/1039441/ https://lwn.net/Articles/1039441/ Fowl Perhaps I'm missing some context here, but I don't quite understand the "losing data" part. <p>If a sending a message times out or is cancelled, isn't it expected that it is "lost" without some retry or other logic? How is this an async specific problem? Excuse my guesstimated Rust syntax for a blocking version of the same thing: <p> <pre> loop { let msg = next_message(); match tx.send_blocking_with_timeout(msg, Duration::from_secs(5))){ Ok(_) =&gt; println!("Sent successfully"), Err(Timeout) =&gt; println!("No space in queue for 5 seconds"), Err(_) =&gt; return, } } </pre> <p>Is the bug that only for timeout errors we print a message and continue, whereas other errors return to the caller? Thu, 25 Sep 2025 09:32:51 +0000 Funny parallel with Python https://lwn.net/Articles/1039440/ https://lwn.net/Articles/1039440/ kleptog <div class="FormattedComment"> <span class="QuotedText">&gt; In those languages, creating the equivalent of a Future (called a promise) starts executing the code right away in a separate thread, and the promise is just a handle for retrieving the eventual result.</span><br> <p> We ran into something similar when migrating old Tornado code to the new asyncio library in Python. When Python did async code via generators/coroutines, the semantics of yield are that generators are run until the first yield. Whereas async methods don't run at all until polled, like Rust. This almost never matters, but it turned out there were a handful of places that expected some of the side-effects to happen straight away, and they broke.<br> </div> Thu, 25 Sep 2025 09:23:05 +0000 Discarding return value, and ignoring return type https://lwn.net/Articles/1039437/ https://lwn.net/Articles/1039437/ taladar <div class="FormattedComment"> Ignoring futures could be useful in unit tests where you only want to test the function creating the future in one test and the future itself in the next one.<br> </div> Thu, 25 Sep 2025 08:57:20 +0000 Discarding return value, and ignoring return type https://lwn.net/Articles/1039431/ https://lwn.net/Articles/1039431/ proski <div class="FormattedComment"> There are three levels of badness here, not two. Discarding a return value in general should be OK, it could be informational (e.g. number of bytes copied). Ignoring Result is worse, it means that the caller doesn't care if the call was successful. I wouldn't do it in serious code, I would at least try to log the error. Ignoring Future is even worse. I cannot imagine why anyone would do, unless it's a test for the compiler, and nobody cares about the runtime behavior. <br> </div> Thu, 25 Sep 2025 08:11:55 +0000 Discarding return value, and ignoring return type https://lwn.net/Articles/1039418/ https://lwn.net/Articles/1039418/ epa <div class="FormattedComment"> I think the problem is that let _ is the standard syntax for “go and do something but ignore its return value” but it ends up meaning “don’t even do that thing, just create the future but then throw it away”. In terms of language semantics this is completely correct, of course, but it’s probably not what the programmer intended. So another level of “yes, really” may indeed be useful. <br> </div> Thu, 25 Sep 2025 06:08:18 +0000 Discarding return value, and ignoring return type https://lwn.net/Articles/1039415/ https://lwn.net/Articles/1039415/ mb <div class="FormattedComment"> I think the problem is this:<br> <p> async fn a() -&gt; Result&lt;x, y&gt; {<br> ...<br> }<br> <p> async fn b() {<br> // Ignore the Result of a().<br> let _ = a(); // What I wrote<br> let _ = a().await; // What I meant<br> }<br> <p> It would be good if the first thing caused a warning by default. It's a common mistake.<br> </div> Thu, 25 Sep 2025 04:52:51 +0000 Rust syntax is only getting worse https://lwn.net/Articles/1039396/ https://lwn.net/Articles/1039396/ sunshowers <div class="FormattedComment"> (I presented the talk -- thanks for featuring it on LWN!)<br> <p> I think the Ok(Ok(()) stuff is quite readable once you're used to it -- nested pattern matching is something to be used sparingly, but is reasonable to avoid added nesting levels (a match statement adds 2 nesting levels). It looks pretty reasonable with syntax highlighting.<br> </div> Wed, 24 Sep 2025 22:18:17 +0000 Discarding return value, and ignoring return type https://lwn.net/Articles/1039390/ https://lwn.net/Articles/1039390/ NYKevin <div class="FormattedComment"> <span class="QuotedText">&gt; he await method returns the underlying value, but you don't have to await. Perhaps there should be a special syntax for "do not await" -- which obviously just returns the Future itself -- and a linter or compiler warning could ensure that you have called one or the other. If the type is known at compile time to be a Future of some kind, the let _ syntax would give a compile-time warning, unless you explicitly call .do_not_await.</span><br> <p> We already have a lint warning for dropping something that shouldn't be dropped, namely #[must_use], which is present on Futures. Clippy will warn you if you write an expression statement that implicitly drops a Future (and you can configure this lint to be deny-by-default if desired, so that it produces a hard compile error instead of a warning). The let _ syntax is the idiomatic way to silence that lint, so you're effectively proposing a second "no, I really mean it" syntax, to doubly silence the warning after it has already been silenced. I'm not sure why that would be any more effective than the existing lint is in isolation. There is, perhaps, some room for marginal improvement, since #[must_use] is quite liberal about what counts as a "use," and a Future-specific warning could be more exacting. But this seems to be well into diminishing returns relative to the increased language complexity.<br> <p> If we think that #[must_use] is not sufficient, then I must concur with daroc: The logical next step is undroppable types, not another layer of "no, I really mean it" syntax. Undroppable types let us make hard assertions that you *must* dispose of an instance in the appropriate way.<br> <p> We might also consider unforgettable (a/k/a unleakable) types, but I think it is much less common to accidentally leak an object than to accidentally drop an object (you either have to construct a refcounted cycle, or use an interface that "obviously" leaks, such as std::mem::forget or Box::leak). Those are only needed if you want to prohibit leaking objects as a safety invariant. In other words, you need them for cases where leaking an object could produce UB. That's pretty unusual, and the only obvious example I can think of is the original (now known to be unsound) API for std::thread::scope. But the new API does not need unleakables, and it was a fairly minor change, so this is not a great motivating use case for them.<br> </div> Wed, 24 Sep 2025 21:25:31 +0000 Rust syntax is only getting worse https://lwn.net/Articles/1039385/ https://lwn.net/Articles/1039385/ jbills <div class="FormattedComment"> If it is getting worse (I disagree), this syntax isn't an example. This syntax (pattern matching) has been in the language for a very long time. Regardless, it is entirely transparent if you have written enough Rust to know how the data types work. You have two functions with two independent sources of errors that are getting nested. You need to unwrap them independently to be able to handle them independently.<br> </div> Wed, 24 Sep 2025 20:28:02 +0000 Discarding return value, and ignoring return type https://lwn.net/Articles/1039384/ https://lwn.net/Articles/1039384/ duelafn <p>That particular situation has a (disabled by default) lint: <a href="https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_untyped">clippy::let_underscore_untyped</a>.</p> <pre> let _ = some_operation(); </pre> <p>Gets a warning "consider adding a type annotation" which goes away if you do this:</p> <pre> let _: Result&lt;_,_&gt; = some_operation(); </pre> <p>Which would catch the async problem. I go one step further and make sure the Ok result type is also specified (<code>Result&lt;u64,_&gt;</code>, but the lint does not enforce that.</p> Wed, 24 Sep 2025 20:20:12 +0000 Discarding return value, and ignoring return type https://lwn.net/Articles/1039370/ https://lwn.net/Articles/1039370/ daroc <p> That is somewhat similar to what the Undroppable Types proposal would do, only not specific to Futures. With that proposal, any type marked as being Undroppable would be a compiler error to discard, including via <tt>let _ = ...</tt>. The only ways to get rid of it would be by consuming it (in this case, doing <tt>.await</tt>), handing it off to another function, or by destructuring it. </p> Wed, 24 Sep 2025 18:45:14 +0000 Discarding return value, and ignoring return type https://lwn.net/Articles/1039359/ https://lwn.net/Articles/1039359/ epa <div class="FormattedComment"> Yes, it's dangerous that a function could return a value that's safe to ignore, and later be changed to return a Future (which isn't safe to ignore). The let _ syntax discards the return value and doesn't make any assertion about the return type either.<br> <p> Maybe the problem is that Future doesn't get special treatment in the type system. The await method returns the underlying value, but you don't have to await. Perhaps there should be a special syntax for "do not await" -- which obviously just returns the Future itself -- and a linter or compiler warning could ensure that you have called one or the other. If the type is known at compile time to be a Future of some kind, the let _ syntax would give a compile-time warning, unless you explicitly call .do_not_await.<br> </div> Wed, 24 Sep 2025 18:02:16 +0000 Rust syntax is only getting worse https://lwn.net/Articles/1039357/ https://lwn.net/Articles/1039357/ Altan <div class="FormattedComment"> Pattern matching is not something new for programming languages, there are better usages of it in other languages but Rust also has nice syntax and support for it.<br> </div> Wed, 24 Sep 2025 17:43:44 +0000 Rust syntax is only getting worse https://lwn.net/Articles/1039343/ https://lwn.net/Articles/1039343/ geofft It's not really worse than, say, a double pointer in C, where the outer pointer can be NULL or point to some inner pointer, which itself can be NULL. You have to check for both of those, and they can mean different things. (Though you can counter that defending syntax by saying "what about double pointers in C" is not much of a defense.) <p> The real thing here is that it would be nice to give names to the two variants, maybe even just at one level, like <pre> loop { match timeout(Duration::from_secs(5), rx.recv()).await { Happened(Ok(msg)) =&gt; process(msg), Happened(Err(_)) =&gt; return, Timeout(_) =&gt; println!("No messages for 5 seconds"), } } </pre> but the tradeoff is that there's a good bit of functionality that works with the pre-existing <a href="https://doc.rust-lang.org/std/result/enum.Result.html"><tt>Result</tt></a> type (<tt>Ok</tt>/<tt>Error</tt>) that people are used to using, and coming up with a new type for this purpose loses all of that. <p> Maybe it'd be nice to have the ability to declare an alias for a type that has new names for the variants but is really just the same type, or something. I think there's also some work in progress to <a href="https://doc.rust-lang.org/std/ops/trait.Try.html">abstract the concept of <tt>Result</tt> into traits</a> which might end up effectively doing this. That page also reminds me that there is an existing <a href="https://doc.rust-lang.org/std/ops/enum.ControlFlow.html"><tt>ControlFlow</tt></a> type that is effectively equivalent to <tt>Result</tt> but might be more suitable here (or might not, I'm not sure!). <p> These particular lines of code would also read better if it were matching on single level of encapsulation, something like <pre> loop { match timeout(Duration::from_secs(5), rx.recv()).await { Ok(msg) =&gt; process(msg), Err(_) =&gt; return, Timeout(_) =&gt; println!("No messages for 5 seconds"), } } </pre> but now you're inventing a brand new type with three variants that has none of the existing behavior of the existing types (e.g., how do you compose it with a normal <tt>Result</tt>? what happens if you use the question-mark syntax on it?), and you're also constraining that second argument to <tt>timeout</tt> to be an async function that returns a <tt>Result</tt>, so you have those two variants. The advantage of the current design is that <tt>timeout</tt> wraps an async function that returns anything. Losing that flexibility would make async Rust code both harder to read and harder to write. Wed, 24 Sep 2025 16:56:35 +0000 Rust syntax is only getting worse https://lwn.net/Articles/1039344/ https://lwn.net/Articles/1039344/ mb <div class="FormattedComment"> Well, it's pattern matching.<br> Many languages support this.<br> <p> Rust doesn't force you to nest patterns like in the above code, though.<br> But it's pretty common, because it actually is quite readable, once you understand pattern matching.<br> </div> Wed, 24 Sep 2025 16:31:16 +0000 Rust syntax is only getting worse https://lwn.net/Articles/1039337/ https://lwn.net/Articles/1039337/ sturmflut <div class="FormattedComment"> Ok(Ok(permit)) and Ok(Err(_)) have got to be some of the worst notations I've ever seen. That's write-only code.<br> </div> Wed, 24 Sep 2025 16:12:38 +0000