A distinct way to unwrap Result<T, E> where E: Into<!>

Result<T, Infallible>, soon to be equivalent to Result<T, !> has been occurring quite often in recent code. The first instinct is to use unwrap on it, knowing it can never panic, but herein lies a maintainability hazard: if the error parameter type at the use site is later changed to an inhabitable one, the unwrap quietly becomes liable to panic.

Therefore, it would make sense to add a conversion to the standard library that would only be applicable to Result with an uninhabitable error type. In earlier discussion, two solutions were put forward:

Add an Into conversion:

impl<T> From<Result<T, !>> for T

Add a new method to Result:

impl<T, E: Into<!>> Result<T, E> {
    pub fn unwrap_infallible(self) -> T {
        match self {
            Ok(x) => x,
            Err(e) => e.into(),
        }
    }
}

I don't have a strong preference for either of the two, but as an extension trait method, unwrap_infallible is already available from a crate I have written. It would be nice to get an equivalent out of the box.

Extending the conversion coverage to E: Into<!> allows crate APIs who feature custom never-types to plug into this without redefining their type as an alias of !, which may break existing code. For the From<Result<T, E>> impl though, the parameterization of E might make it too blanket.

7 Likes

I recall the following working at some point, though I can't find the feature to get it working on the playground right now:

let res: Result<_, !> = f();
let Ok(ok) = res;

For the Err=! case, this feels like the correct solution. I also think that this worked for any uninhabitable Err variant, though I can't recall. If it doesn't, the conversion to the standard Result<T, !> seems like it should come down to the library to provide.

4 Likes

This takes a let statement to write, so f().unwrap() would still be tempting ergonomics-wise. The problem is not that it's impossible to unwrap with statically proven infallibility, because in stable you've been always able to do f().unwrap_or_else(|never| match never {}). It just should be almost as convenient as .unwrap().

I should probably rename the method to unwrap_always to make it 4 characters shorter and spare the coders from remembering obscure adjectives (though Infallible is sort of Rust legacy by now).

1 Like

That's a very broad From impl, which worries me while we don't have intersection impls or whatever.

I like unwrap_infallible; it fits the current pattern of unwrap_or and friends.

2 Likes

Thank you. It's enough encouragement for me to morph this into an RFC.

Aside, it's pretty fun that such args can be infallibly matched:

impl<T> From<Result<T, !>> for T {
    fn from(Ok(t): Result<T, !>) -> T { t }
}
11 Likes

That's exhaustive_patterns.

Not sure how I feel about this but I think it's worth proposing as an alternative, is there any problem with just allowing:

fn g() {
    let x = f()?;
    ...
}

when f() is infallible? This also means if g() does return a result refactoring f() to be fallible would not need to touch this call site.

This does not completely eliminate the refactoring hazard, while making part of it more removed from the call site. If any type satisfies From<!>, the return type of g() can be changed to Result compatibly with the call site. Then if f() is made fallible and its error type happens to be convertible to that of g(), you've got a failure path you may not have intended to have.

The usual semantics of ? meaning "can return an error early here" would obscure the intent to make use of infallibility.

2 Likes

Filed the RFC: https://github.com/rust-lang/rfcs/pull/2799

Thanks to everyone who contributed to this discussion.

I think this may introduce a new way to break library compatibility between versions.

Such as changing from Result<T, E> into Result<T, !> and vice versa. Any idea on this? Maybe add to API guidelines?

That is already a backwards incompatible change