[Pre-RFC] From<T> for Result<T, E>


#1

Proposal

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

Motivation

This makes it easy to accept closures that can return errors but might not.

fn do_something<F: Fn() -> Into<Result<i32, SomeError>>(f: F) {
    let result = (f)().into();
    // ...
}

fn main() {
    do_something(|| 0);
    do_something(|| Ok(0));
    do_something(|| Err(..));
}

Alternatives

Users can just write/implement a custom trait. It’s not hard but it’s annoying.

Drawbacks

Coherence? I don’t think it will overly restrict any other impls but I’m not positive.


Bool::into_option(closure) yields None or Some(closure())
#2

Drawbacks: more implicit magic which can be confusing.


#3

This should only overlap with other impls of Result<T, E>. For example, you couldn’t impl the inverse (impl<T, E> From<E> for Result<T, E>). That’s because it wouldn’t be able to dispatch if T == E.

Without specialization or mutual exclusion, this would overlap with any impl for Result. With mutual exclusion, this wouldn’t overlap with an impl of From<E> where E and T were bound mutually exclusively. Specialization could open up any number of coherent impls depending on how loose specialization becomes.


#4

It’s a lot less magical than things like deref coercion and the try! macro. You have to explicitly state that you want something that can be converted into a result and then explicitly convert it into a result. Although yes, I guess it still counts as magic.


#5

try! is magic, but it’s not implicit magic to the same extent, at least when it comes to Result (as opposed to the From magic it also does): if you know what try! does, there’s no local ambiguity about whether you wanted to add or remove a layer of Result, whereas here there is.


#6

try! uses From internally and will do magical things like box errors. Also, I don’t see where the ambiguity here is. You have to make an explicit into() call to convert something that might be a result onto something that is a result.


#7

I’m not sure on the coherence aspects, but I agree that this doesn’t seem particularly magical to me.


#8

I am a little skeptical of the motivation purely because I’m not sure how common it is. On the other hand, the impl itself does feel right to me—assuming there’s no coherence issues I’m missing.


#9

So, I’ve run across a better motivation: Futures. The futures-rs crate uses Results everywhere, even in cases that can’t error. Furthermore, futures-rs often accepts Results as function arguments (most rust crates just return them).

Motivation

Typing Ok(value) everywhere is annoying and makes it harder to understand what’s going on in the code. Whenever I see an Ok(...) I immediately assume that something can fail and start looking for the Errs.

Motivating Examples

For example, in

@alexcrichton and @stepancheg considered making stream::repeat take a Result but decided to take a value directly (possibly adding a stream::repeat_err method in the future) because the common case, stream::repeat(Ok(value)), isn’t very ergonomic.

However, if we had a From<T> for Result<T, E> implementation, one could implement fn repeat<T, E, R: Into<Result<T, E>>>(item: R) -> impl Stream<Item=T, Error=E>. The user could call either stream::repeat(value) or stream::repeat(Err(e)). This would also apply to other helper functions like stream::once and done.

Additionally, one could use this as described in my first post to simplify using many of the adapters in futures-rs. For example, you could replace:

my_stream.for_each(|item| {
    println!("{}", item);
    Ok(())
});

with:

my_stream.for_each(|item| {
    println!("{}", item)
});

Drawbacks

One potential (big) drawback is type inference. Depending on the use-case, the error may be unconstrained.

Alternatives

  1. An alternative is to implement impl<T> From<T> for Result<T, !>. This would avoid the type inference problem. Unfortunately, it would make this feature less useful (maybe? I’m not actually sure it would be a huge problem and the benefits may outweigh the drawbacks).

  2. Default "impl"s (default type parameters for selecting trait implementations (type inference hints?)). That is, impl<T, E=!> From<T> for Result<T, E>. However, this would require a lot of thought and design work and I kind of doubt it will happen any time soon.


#10

IMO this somewhat hinders readability of Result-propagating code. One of the things I love the most about Rust is the explicit approach to error handling; you see explicit ?'s, try!'s, Ok(...)'s all along the success path, and similar things for the error edges of the CFG. With this some of the “landmarks” are turning into generic ones like into. It’ll at least take some time for people’s eyes to adjust, as while the error edges are not touched, the success edges are becoming less noticeable.

However in the futures case I’d admit it actually improves the flow, since in stream-oriented code the branches are expressed directly with operations on the stream instead, in which case the Ok's are visually redundant.


#11

It’ll at least take some time for people’s eyes to adjust, as while the error edges are not touched, the success edges are becoming less noticeable.

That would be the case for passing results into functions (e.g., stream::repeat_item(value) versus stream::repeat_item(Err(error)) but not for returning results from a single function/closure. This only kicks in when a function never returns an error (you can’t return value and Error(error) from the same function) so there would be no error path. Actually, one of my motivations is to let the reader easily determine that there is no error path.

(but thanks for bringing this up)


#12

Well yeah if this is the case then indeed a lot of Ok's will be eradicated. I think it’s then everyone’s responsibility to manually put in Ok's when the meanings actually become less clear.

BTW I come from a Python background so I find the current explicit Ok usages better than, well, implicit conversions. Seems a matter of personal taste then. :smile:

Edit: apparently I saw the discussion above on the implicitness of various “magics”; IMO one of the “darker” magics, like deref coercions, in fact is obvious to anyone paying adequate attention. Because Rust have no overloading, every function has exactly one signature and that’s explicit; at the call site, every binding has a determined type, which is explicit too. So if any pair of expected and actual types mismatches, and a & is in, there must be a deref coercion taking place. At least to my eyes, they’re standing out crystal clear; nothing magical and implicit. Now there’s analogy for Result conversions too, maybe one just needs to remember one more rule for that. Not removing the Ok's reduces the cognitive load just for a little bit more typing, though.


#13

Actually, I’ve been bitten by Deref magic before. Basically, if you have a type Foo which dereferences to a type Bar, and a trait Baz implemented on both Foo and Bar, it’s really easy to accidentally dereference &Foo to &Bar. For example, in my template engine (horrorshow), I used a wrapper type Raw<T>(value: T) to say “don’t automatically escape the special characters in value when rendering it”. Unfortunately, I also implemented Deref<T> for Raw<T> which meant I would occasionally (accidentally) dereference Raw("<b>my raw string</b>") to "<b>my raw string</b>" and render it as &lt;b&gt;my raw string&lt;/b&gt;.