libs-team icon indicating copy to clipboard operation
libs-team copied to clipboard

`Result::ignore`

Open GrigorenkoPV opened this issue 1 year ago • 7 comments

Proposal

Problem statement

Result is a #[must_use] type, but there is no idiomatic way to ignore it. Consider

fn try_foo() -> Result<A, E> { ... }
fn main() {
    try_foo();
}

This will give you a warning, even if you don't care about the result. Now you have to discard it somehow.

Alternatives

One may try

let _ = try_foo();

but if we now change signature to

async fn try_foo() -> Result<A, E> { ... }

this will result in the Future silently being dropped without polling it even once. It doesn't help that Future is a #[must_use] type too. You can try to combat this with

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

but that's quite mouthful and is not something that will come to your mind right away.

The Rust community seems to have developed a trick for this situation, which is using .ok(). This works because this method (and the returned Option<T> type) is not marked #[must_use] for some reason (unlike is_ok()) and is super unintuitive when you read this for the first time.

Motivating examples or use cases

Quite a lot even in the rustc itself. Add #[must_use] to Result::ok and see the number of errors you get.

Solution sketch

  1. Add the following method to Result:
fn ignore(self) {}
  1. Mark .ok() & .err() as #[must_use] after ignore is stabilized.

Maybe name it ignore_result() instead, for clarity.

Links

Example of the problem with unpolled Futures (and general discussion of the situation): https://users.rust-lang.org/t/what-is-the-best-way-to-ignore-a-result/55187

A "what is your favorite way to ignore a Result?" poll I've randomly stumbled upon today: https://t.me/psauxww/1259 . Would not exist if there was an idiomatic way to do it or if a Rust programmer did not need to ignore a Result every once in a while.

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.

GrigorenkoPV avatar May 23 '24 12:05 GrigorenkoPV

this will result in the Future silently being dropped without polling it even once.

Why not let _ = foo().await;?

the8472 avatar May 23 '24 12:05 the8472

Why not let _ = foo().await;?

I think the point is that if the API evolves (goes from fn() -> Result<A, B> to async fn() -> Result<A, B>) then the previous usage of let _ will continue to compile but now mean something different: ignoring the future instead of ignoring the Result.

shepmaster avatar May 23 '24 14:05 shepmaster

Why not let _ = foo().await;?

I think the point is that if the API evolves (goes from fn() -> Result<A, B> to async fn() -> Result<A, B>) then the previous usage of let _ will continue to compile but now mean something different: ignoring the future instead of ignoring the Result.

Precisely. let _ = does not get along well with any type changes to the return value. It's just that the Future is the most egregious example, because accidentally dropping it significantly changes the control flow.

GrigorenkoPV avatar May 23 '24 15:05 GrigorenkoPV

Oops, wrong button

GrigorenkoPV avatar May 23 '24 15:05 GrigorenkoPV

You would still have a problem if the function changed from Result<T, Infallible> to a real error you should handle.

There are also many other #[must_use] types and functions that should be considered. I don't have any more general ideas, apart from explicit let _: Type as you mention, but I'm sure we wouldn't want to add ignore everywhere.

cuviper avatar May 23 '24 15:05 cuviper

You would still have a problem if the function changed from Result<T, Infallible> to a real error you should handle.

Won't there be some special unwarp for Result<_, !> once ! geta stabilized?

Also, this is still a problem with current status quo of .ok().

Finally, this ACP is partially about adding #[must_use] to .ok() and .err(), which is blocked on having no idiomatic way to ignore a Result.

GrigorenkoPV avatar May 23 '24 15:05 GrigorenkoPV

Won't there be some special unwarp for Result<_, !> once ! geta stabilized?

Result::into_ok

shepmaster avatar May 23 '24 15:05 shepmaster

I'm really sympathetic to this one, because I managed to write let _ = func(); once when I meant to write let _ = func().await;, resulting in a hard-to-track-down bug.

joshtriplett avatar Apr 29 '25 18:04 joshtriplett

We discussed this in the @rust-lang/libs-api meeting. Considering we have dedicated language syntax for ignoring results (_ = ) and that we now have a clippy lint against futures that are ignored, we don't feel that a new method is justified.

Amanieu avatar Apr 29 '25 20:04 Amanieu