Get rid of unused result warning for Result<(), std::convert::Infallible>?

I doesn't seem to make sense to warn here.

Is there a way around that?

unwrap() it. By definition it won't panic.

If you're nervous about the error type changing in the future and biting you, you can also destructure it with a match. The Err arm shouldn't be required because the enum is empty.

That's not the point. This is returned from a public API, eg futures::SinkExt::send, on a Sink that is infallible. Given this is infallible, it would be nicer if users could write:

obj.send( something ).await;

// instead of (which is better than unwrap btw)
//
let _ = obj.send( something ).await;

without getting a nonsensical warning. Maybe the lint should just be changed.

You should match Infallible:

fn handle(r: Result<String, std::convert::Infallible>) -> String {
   match r {
       Ok(it) => it,
       Err(it) => match it {},
   }
}

playground

2 Likes

If the Ok value is a tuple, why would you do such a thing?

To verify, at compile time, that the err case is impossible.

I'm sorry, but I don't understand. std::convert::Infallible is a type that can't be constructed. So the type system already guarantees that an Err variant will never exist. The Ok value is a tuple, so contains no useful data. The method would just return a tuple if it wasn't a trait impl.

I feel that warning for an unused result in this case is wrong.

A Result is a Result. There's nothing about the must_use lint that can selectively trigger based on what types the Result is parameterized over. And while a Result<(), Infalliable> happens to have the same number of inhabitants as a plain (), those are still different types with different semantics.

6 Likes

I personally use .unwrap_or_else(|x| match x {}). It's like the suggested .unwrap(), but checked at compile time :slight_smile:

The longer option would be to have:

trait VoidType {
    fn unreachable (self: &'_ Self)
      -> !
    ;
}

impl VoidType for ::core::convert::Infallible {
    fn unreachable (self: &'_ Self)
      -> !
    {
        match *self {}
    }
}

impl<T, E> ResultExt for Result<T, E> {
    type T = T;
    type E = E;
}
trait ResultExt {
    type T;
    type E;
    fn unwrap_noop (self: Self)
      -> Self::T
    where
        Self::E : VoidType,
    {
        self.unwrap_or_else(|err| err.unreachable())
    }
}
4 Likes

Using a helper function:

fn ex_falso_quodlibet<T>(x: Infallible) -> T {
    match x {}
}

fn handle(r: Result<String, Infallible>) -> String {
   r.unwrap_or_else(ex_falso_quodlibet)
}
2 Likes

Note that .unwrap() not just never panic, it doesn't holds any flag to determine if its failed so branching on it is not even possible.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8abcb76343d6d2274e4f90358a4da7a6

1 Like

I have made a crate that does this: Unwrap Infallible Results With All Convenience And No Fear

There is no separate trait to implement for infallible types and a blanket extension impl for Result as in @Yandros's design. I don't expect there to be many custom never-types that are not just reexports of std::convert::Infallible. But I have added a feature-gated blanket impl for Result<T, E> where E: From<!>. This could be morphed into an inherent generic method of standard Result, if From<!> becomes a conventional impl to tag types with never occurring values.

This very very much the wrong bound. Every type will impl From<!>.

What you want is !: From<E>

enum Void {}

impl From<Void> for ! {
    fn from(x: Void) -> ! {
        match x {}
    }
}

but it still doesn't mean much as inhabited type can still impl it using panic

struct Foo;

impl From<Foo> for ! {
    fn form(x: Foo) -> ! {
        panic!()
    }
}

I see two options:

  1. use a if let

    fn main() {
        if let Ok(v) = function_that_returns_a_result() {
            dbg!(v);
        }
    }
    
  2. There is the exhaustive_patterns feature (which works obviously only on nightly)

    #![feature(exhaustive_patterns)]
    
    fn main() {
        let Ok(v) = function_that_returns_a_result();
        dbg!(v);
    }
    

You are right, I got it the other way around.

This "issue" is not specific to this case: you can make an arbitrary type impl many traits by having the methods diverge rather than return. That's "just" the implementor's fault. If it just loops indefinitely or aborts the process then there is no memory unsafety (if it panics and unwinds the stack, however, then it could lead to memory unsafety, provided someone used unsafe in an unsound manner, which is not the case here).

But yes, the guarantee that a unreachable() -> ! method can help get zero-cost unwrapping is only true with cooperative implementations; otherwise it may behave as a classic .unwrap().

Another example: the whole Futures architecture is based on implementors not blocking when polled, which cannot be verified, and thus requires trusting the implementations (cooperative concurrency).

1 Like

I think I got it covered:

#[cfg(feature = "blanket_impl")]
impl<T, E: Into<!>> UnwrapInfallible for Result<T, E> {
    type Ok = T;
    fn unwrap_infallible(self) -> T {
        match self {
            Ok(v) => v,
            Err(e) => e.into(),
        }
    }
}

Curiously, there is no impl From<Infallible> for ! yet. But this could wait until ! is stabilized and Infallible becomes a type alias for !.