Allow the `?` operator to be used on `()` in generic contexts

Earlier today, I wanted to make a function in a trait generic over its 'resultness' (i.e., generic over whether it has Result<(), SomeError> or just the unit type () as its Output type). However, when using this function in a context that is generic over the Output type, it should still be possible to short-circuit using the ? operator.

Finally, I opted for defining my own Unit type to enable this and did something akin to:

#![feature(never_type, try_trait_v2)]
use std::ops::{ControlFlow, FromResidual, Try};

struct Unit;

impl FromResidual<!> for Unit {
    fn from_residual(_: !) -> Self {
        unreachable!()
    }
}

impl Try for Unit {
    type Output = ();
    type Residual = !;

    fn branch(self) -> ControlFlow<!> {
        ControlFlow::Continue(())
    }

    fn from_output(_: ()) -> Self {
        Self
    }
}

trait Foo {
    type Output: Try<Output = ()>;

    fn foo(&self) -> Self::Output;
}

struct Bar;

impl Foo for Bar {
    type Output = Unit;

    fn foo(&self) -> Self::Output {
        Unit
    }
}

struct Baz;

impl Foo for Baz {
    type Output = Result<(), String>;

    fn foo(&self) -> Self::Output {
        Err("Baz".to_string())
    }
}

fn run_foo<T: Foo>(foo: T) -> T::Output {
    foo.foo()?;
    println!("This should only run for Bar but not for Baz");
    foo.foo()
}

Obviously, this would be much nicer, if <Bar as Foo>::Output would be () instead of Unit. One way to make that possible would be to put these FromResidual and Try as they are written above for Unit into the standard library for (). However, that would mean that the ? operator suddenly becomes usable on every value of type (), which would obviously not be very desirable. Would there be any other way to make this work in a generic context but not for normal unit values?

Previous libs-api discussion about this:

1 Like

Ah, thanks. I have yet to learn all the different locations where discussions about language and standard library design could happen. It’s a bummer that this is not being considered in conjunction with a strong lint.

Have you considered using a generic for the error type of Result and passing Infallible for it if the operation can't fail?

I'd expect the generic Result<T,Infallible> comes up more than your Unit, which yeah afaik works exactly like Result<(),Infallible>.

Of course, I could just do that. However, you still have to call .unwrap() or something on Result<(), Infallible> if you don't want to have warnings like unused `Result` that must be used.

There will soon™ be some new options for this thanks to Stabilize `min_exhaustive_patterns` by Nadrieril · Pull Request #122792 · rust-lang/rust · GitHub

But it's true that Ok(()) = thing_that_returns_result_of_unit_and_infallible(); is still longer than the unit-returning version.

We can probably modify unused_must_use to not trigger on Range<(), Infallible> etc, though that doesn't help the generic case.

Well, in the generic case, you'd have to unwrap/? anyway so if unused_must_use didn't trigger on Result<T, Infallible>, unless T is itself annotated with #[must_use], that would be a nice improvement already.