|
|
Subscribe / Log in / New account

Discarding return value, and ignoring return type

Discarding return value, and ignoring return type

Posted Sep 24, 2025 18:02 UTC (Wed) by epa (subscriber, #39769)
Parent article: Canceling asynchronous Rust

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.

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.


to post comments

Discarding return value, and ignoring return type

Posted Sep 24, 2025 18:45 UTC (Wed) by daroc (editor, #160859) [Link]

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 let _ = .... The only ways to get rid of it would be by consuming it (in this case, doing .await), handing it off to another function, or by destructuring it.

Discarding return value, and ignoring return type

Posted Sep 24, 2025 20:20 UTC (Wed) by duelafn (subscriber, #52974) [Link] (1 responses)

That particular situation has a (disabled by default) lint: clippy::let_underscore_untyped.

    let _ = some_operation();

Gets a warning "consider adding a type annotation" which goes away if you do this:

    let _: Result<_,_> = some_operation();

Which would catch the async problem. I go one step further and make sure the Ok result type is also specified (Result<u64,_>, but the lint does not enforce that.

Discarding return value, and ignoring return type

Posted Sep 25, 2025 11:22 UTC (Thu) by kpfleming (subscriber, #23250) [Link]

That's really useful and news to me, thanks for posting it!

Discarding return value, and ignoring return type

Posted Sep 24, 2025 21:25 UTC (Wed) by NYKevin (subscriber, #129325) [Link] (2 responses)

> 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.

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.

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.

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.

Discarding return value, and ignoring return type

Posted Sep 25, 2025 4:52 UTC (Thu) by mb (subscriber, #50428) [Link]

I think the problem is this:

async fn a() -> Result<x, y> {
...
}

async fn b() {
// Ignore the Result of a().
let _ = a(); // What I wrote
let _ = a().await; // What I meant
}

It would be good if the first thing caused a warning by default. It's a common mistake.

Discarding return value, and ignoring return type

Posted Sep 25, 2025 6:08 UTC (Thu) by epa (subscriber, #39769) [Link]

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.

Discarding return value, and ignoring return type

Posted Sep 25, 2025 8:11 UTC (Thu) by proski (subscriber, #104) [Link] (2 responses)

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.

Discarding return value, and ignoring return type

Posted Sep 25, 2025 8:57 UTC (Thu) by taladar (subscriber, #68407) [Link] (1 responses)

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.

Discarding return value, and ignoring return type

Posted Sep 25, 2025 21:26 UTC (Thu) by intgr (subscriber, #39733) [Link]

I think in that case writing `drop(create_future())` would make the intention clearer than assigning to _

Discarding return value, and ignoring return type

Posted Sep 25, 2025 13:51 UTC (Thu) by Brezak (subscriber, #174007) [Link]

There's the clippy lint clippy::let_underscore_future, that stops you if you discard a future without awaiting it. It's even warn by default.


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds