Thoughts on adding `Option::err_or` and `Option::err_or_else` to libstd

#1

I recently found myself needing to do the following

match opt {
    None => Ok(()),
    Some(e) => Err(e)
}

But I think it would be nice to do this with opt.err_or(())

It seems odd to me that the following exist

  • Option::ok_or
  • Option::ok_or_else

But not the corresponding

  • Option::err_or
  • Option::err_or_else

I feel like they belong for both completeness sake, and usefulness. (Albeit this is the first time I have needed them, and I have used the others a lot more often).

Does anyone have any thoughts on this?

4 Likes

#2

This is odd. Usually Some(ok) is the success case, so you use ok_or(err). I haven’t seen Some(err).

I’m afraid it’d encourage functions returning Option<Error> instead of Result<(), Error>, and that would end up working backwards with the ? operator.

6 Likes

#3

That is a good point. Interestingly, I need it for the very case of a function returning a Result<(), Error>. My use case is where I am parallelizing a task on a bunch of data and end up with an iterator of Results. I then

fn my_function() -> Result<(), Error> {
    // some setup code ...

    let (tx, rx) = std::sync::mpsc::channel();

    // some code to spawn threads and parallelize stuff ...

    rx.iter()
        .take(elements.len())
        .filter_map(Result::err)
        .next()
        .err_or(())
}

I’m sure there might be better way of doing what I want to do, but this is how I ended up with wanting Option::err_or.

0 Likes

#4

.map(|_| ()).collect() does what you want. Collect has special handling for Results (stops on first error) and special handling for () (it knows to flatten them).

3 Likes

#6

This works, but it is not at all clear why at first glance. Turning it into a separate function might still be worth it to improve readability.

0 Likes

#7

Yeah, I’ve occasionally needed to flip the meaning while also going from Option to Result. A sort of “transpose” if you will.

I would never, ever, have figured out that nonsense map.collect() thing though

1 Like

#8

https://doc.rust-lang.org/nightly/std/option/enum.Option.html#method.transpose

0 Likes

#9

No not like that XD

That’s the wrong type.

1 Like

#10

https://doc.rust-lang.org/nightly/std/result/enum.Result.html#method.transpose

1 Like

#11

Nope, still wrong. I want flat to flat, no layers building when going between forms

0 Likes

#12

Then why did you say transpose, that confused me. For that you can use Result::ok or Option::ok_or

0 Likes

#13

well, I did not know about the existing methods literally named “transpose”. I might also have used the word in error somewhat.

Option::ok_or turns Some into Ok and None into Err. Result::ok turns Ok into Some and Err into None. What I wanted in the past was what’s described in the match in the first post: Some becomes Err, and None becomes Ok (and the similar inverse for Result to Option).

0 Likes

#14

Oh, sorry my bad.

0 Likes

#15

A probably-crazy idea: impl Not for Result that turns a Result<A, B> into a Result<B, A>.

3 Likes

#16
fn my_function() -> Result<(), Error> {
    // some setup code ...
    let (tx, rx) = std::sync::mpsc::channel();
    // some code to spawn threads and parallelize stuff ...
    use std::ops::Not;
    rx.iter()
        .take(elements.len())
        .filter_map(Result::err)
        .next()
        .ok_or(())
        .not()
}

I admit you can read it, but it still seems odd.

My main argument for Option::err_or() and Option::err_or_else would be that Result::err() already exists and adding them would make the API less surprising.

0 Likes

#17

I would prefer we added a flip method to Result<T,E> that looks like this:

impl<T,E> Result<T,E>{
    fn flip(self)->Result<E,T>{
        match self {
            Ok(v)=>Err(v),
            Err(e)=>Ok(e),
        }
    }
}

instead of having Result<T,E> implement Not.

I don’t know how useful it would be to add this method,since I’ve rarely needed to do it.

1 Like