Implement From<&()> (and potentially From<&mut ()>) for the unit type (). This would allow the question mark operator (?) to automatically "unwrap" references to unit types, smoothing over ergonomics when working with Result.
Motivation
A common pattern in Rust is to reference a Result to avoid moving out of a value, often via .as_ref(). This transforms a Result<T, E> into a Result<&T, &E>.
When the error type is the unit type (), the resulting type is Result<&T, &()>. However, because &() does not implement Into<()>, the following code fails to compile:
fn handle_data(result: &Result<Data, ()>) -> Result<(), ()> {
let data = result.as_ref()?; // Error: the trait `From<&()>` is not implemented for `()`
Ok(())
}
Currently, developers must resort to awkward workarounds:
result.as_ref().map_err(|_| ())?
result.as_ref().map_err(|&e| e)?
Converting to an Option via .ok()?, which loses the semantic intent that the operation was a Result.
Given that () is a zero sized type and carries no information, a reference to it is logically equivalent to the value itself.
Proposed Guide-level Explanation
We add an implementation to core that allows &() to be converted into (). Because the ? operator uses From::from for error conversion, this allows Result<T, &()> to be used in functions returning Result<_, ()>.
Why do you have Results using () as their error type? I feel like it’s more common to use a unit struct if there’s really nothing you have to say about the failure, but that doesn’t compose. Without that, the motivation seems kind of weak, even though I don’t see any harm in it.
I have an (incomplete) project that uses Result<(), ()> all over the place, behind a type alias:
//! The style in this application is to log errors (i.e. report them
//! to the console) as close as possible to the point where they occur,
//! and then pass to higher layers only an indication that the error
//! happened. Those higher layers expect to get a [`LoggedStatus`],
//! which is a type alias for `Result<(), ()>`. We also provide the
//! more general [`LoggedResult<S>`] which is `Result<S, ()>`.
I have the exact same use case as @zackw . My use case is a gui application, I handle the error by logging as soon as I know for sure know how to handle it and then maybe present the user with a popup message. I don't want to lose the fact that this is a Result and higher up in the call stack (which is usually just one level - page rendering) should not attempt to log or understand the error, just know that an error occurred. So yes there really is nothing to say about the failure, which is the motivation for using.
But regardless if you believe the motivation in the use case is sound. It is more about if it makes sense for &() to be converted into () through a From implementation. I.e. does &() carry some significance or disconnect that converting into a () would not make sense. Since () signifies no meaningful value, &() a reference to no meaningful value should therefore be easily convertible to (). But maybe I could have highlighted that more in the original post.
Rustc has an approach for this that helps avoid mistakes which, I think, also discourages using () for this.
Basically, it has a ZST struct ErrorReported {} that's only constructable via something like ErrorReported::i_solumnly_swear_i_actually_did_report_one(), but isCopy.
That way if you use the normal error reporting paths it gives you back the ErrorReported you need, which you put in your Err. But also if you've already reported one, you can return that Err(ErrorReported) as many times as needed.
But that means if you don't already have an ErrorReported, you can't accidentally just Err(()) thinking that you actually reported one. It means having in instance is a type-level "proof" of having reported something. That way you don't need to be super careful that everyone follows the convention; you just rely on the type checker. (And the easy code review of noticing that someone called i_solumnly_swear_i_actually_did_report_one when they shouldn't have.)
TL/DR: use a unit struct error if you want, but don't use ().
If I ever get back to that project I'll think about using this suggestion. There's pretty tricky error handling code in several places and it might make it easier to be sure it's correct.