Why isn't `Result<T, Infallible>: From<T>`?

My question is in the title. Let me maybe explain why I was expecting it to be implemented.

The std::ops::Div trait looks like this:

pub trait Div<Rhs = Self> {
    type Output;
    fn div(self, rhs: Rhs) -> Self::Output;
}

The Output could be a Result, perhaps because the implementation checks for division by zero.

I was naively expecting that I could write code that is generic over the return type of div() being a T or a Result<T, E>, simply via a bound of Output: Into<Result<T, E>>, E: Error, and by banking on Result<T, Infallible>: From<T>.

I was surprised to learn that this last trait is not implemented. So why isn't there a blanket implementation for turning a T into a Result<T, Infallible>, like we have it for Option<T>?

1 Like

Presumably because it’s not clear if there should instead be a From<E> implementation for Result<Infallible, E>. (Even though “infallible” doesn’t have quite the right name, until the never type is stabilized, it’s also the best choice for the Ok type of something which never succeeds. Keep in mind that once ! is stabilized, Infallible will be an alias for !, and it doesn’t seem like a good idea to prioritize Result<T, !> over Result<!, E>.)

You could make your own trait to use instead of From.

2 Likes

it’s not clear if there should instead be a From<E> implementation for Result<Infallible, E>

I see, those two blanket impls would overlap for E=Infallible, so we can't have both. Thank you, that is helpful!

it doesn’t seem like a good idea to prioritize Result<T, !> over Result<!, E>

I agree that in the type system there definitely is this symmetry, but practical usage is skewed. "Return value of an operation that never fails" seems much more common than "error of an operation that never succeeds". As you noted, we have a name for the former case ("Infallible"), but not the latter.

1 Like

When dealing with core/alloc/std, it's important to err on the side of caution when it comes to the API due to the consequences of not being able to go back. This is that much more true when dealing with a blanket trait implementation.

Option<T> didn't always implement From<T>, but instead was added in Rust 1.12; even then, there was some pushback. A couple of possibly "weird" consequences of it are:

  • None does not get mapped to None Ă  la Option::flatten but instead Some(None).
  • Uninhabited types (e.g., !) get mapped to Some. This is not that weird since the only way to "construct" an instance of such a type is via a panic (possibly indirectly via infinite recursion) or infinite loop; thus observing the oddity that a thing that can never exist gets mapped to Some instead of None (i.e., the variant used to typically represent the "lack" of a value) is not possible.

Personally, I think the slight improvement in ergonomics is not worth the additional API exposure and occasional (even rare?) preference that Infallible gets mapped to Result::Ok. You can always suggest this impl on GitHub or even the Internals Forum if it hasn't already been suggested; but if I had to guess, I would guess it would be rejected.

On a more theoretical level, I'd argue that Result<T, ()> is in some sense more similar to Option<T> than Result<T, Infallible>[1]; thus Option<T>: From<T> isn't particularly relevant. Admittedly, I find this line of logic less convincing than above.


  1. enums can be viewed as sum types where None is a unit type designed to represent a missing value but is very much inhabited with a single value like () otherwise you wouldn't be able to construct it. Yes, in Rust this isn't technically true; but there was once a request for enum variant types. ↩︎

Crates can implement From<LocalTy> for Result<What, Ever>, so adding the implementation would be a breaking change.

Also, that would only work ergonomically due to the "prefer single implementation candidate" behavior that causes inference breakages with AsRef and the like. Without it, the E is ambiguous without turbofish. (Or when there's more than one candidate.)

2 Likes

True, but then Result<T, !> ≈ T and impl From<T> for T so you just end up in the same place, so yeah, you need a stronger reason.

Indeed, and I find that to be a more useful justification for why Result<T, Infallible> should implement From<T>; albeit still not strong enough since you'd have both Result<T, !> and Result<!, T> being approximately equivalent to T; thus have two conflicting impl From. Additionally even if one were to pick one of the From impls (e.g., T getting mapped to Ok), you'd need to also have T: From<Result<T, !>> in the interest of symmetry.

Personally, I'm wary of preferring one enum variant over another when mapping an instance of a type to it when the variants are based on the same inner type(s). I don't think Result<String, String> should implement From<String>[1] since both variants seem appropriate even if Ok is the more used/common variant. Here the fact that Infallible/! can be used as either type argument (even both as rare as that is) is enough to give me pause.

Now if Rust had a way to bound a type parameter using something like ≠ Infallible, then I'd not have a problem. Of course if that existed, one would likely want Result<T: T ≠ T2, T2>: From<T> + From<T2> where T gets mapped to Ok and T2 gets mapped to Err.


  1. I also wouldn't want T: From<Result<T, T>> even though there is no conflict. ↩︎

I could see some wanting that one... but it's also breaking change and conflicts with the topic of this thread, too. :slightly_smiling_face:

(I don't think I've ever wished for that trait impl per se, but I have wished for a shorter spelling than .unwrap_or_else(|e| e).)

1 Like

The biggest problem is probably just what the name should be: .unwrap_whatever()? .ok_or_err()? :face_savoring_food:

Thanks all, I've enjoyed reading this discussion.

For the record, I was not suggesting that the std lib should introduce the impl (I was aware that this would be a breaking change, thanks for pointing out again). I was more trying to understand the reasoning why it was not done from the beginning.That question has been answered. Reading the github thread linked by @philomathic_life was especially instructive.

.into_inner() could work. (BTW there's already unstable into_ok() and into_err() for Result<T, !> and Result<!, T>.)

1 Like

It was proposed and rejected in 2022 as into_ok_or_err, so it's unlikely that it gets added.

Funny to see someone else coming up with .unwrap_whatever()! And the pointer to partition_point over binary search did actually handle the only use case I've seen.