Using ? with option as well as result in the same method

I have a bigger method in which I receive data from other methods some of which return Options while others return Results. In my bigger method, I don't really want to handle the problematic cases, instead I want to pass the various error or None values as errors back to my caller.

It seems like if I only have Options or if I only have Results, the bigger method's implementation is very simple by using ? whenever I receive something that I want to check if it has unexpected value. But if I try to mix these, then suddenly the simple code becomes significantly more complicated with the need of using ok_or-s and the need of creating new errors unnecessarily.

I have demonstrated these two behaviors in the following playground:
Problematic code
Here method a() is the bigger method that has options as well as results from its called methods, while method e() shows the way simpler situation when the called methods agree on passing back results.

I can't seem to find any ways of simplifying method a() to a similar level to method e(). Any pointers where I should look?

edit: corrected link to playground

Using ok_or (or ok_or_else) doesn’t prevent you from still keeping everything in a single expression. The repetition of the lengthy std::io::Error::from_raw_os_error(22) can be avoided to keep things concise.

E.g.

fn a() -> Result<String, Box<dyn std::error::Error>> {
    let err = || std::io::Error::from_raw_os_error(22);
    let mut hw = String::new();
    hw.push_str(c()?.as_str());
    hw.push(' ');
    hw.push_str(b().ok_or_else(err)?.get_data().ok_or_else(err)?.as_str());
    Ok(hw)
}

or

fn a() -> Result<String, Box<dyn std::error::Error>> {
    let err = std::io::Error::from_raw_os_error;
    let mut hw = String::new();
    hw.push_str(c()?.as_str());
    hw.push(' ');
    hw.push_str(b().ok_or(err(22))?.get_data().ok_or(err(22))?.as_str());
    Ok(hw)
}

If you need error 22 a lot, you can help yourself with an extension trait to create an even shorter method.

trait OptionExt {
    type Ok;
    fn or_22(self) -> Result<Self::Ok, std::io::Error>;
}
impl<T> OptionExt for Option<T> {
    type Ok = T;
    fn or_22(self) -> Result<T, std::io::Error> {
        self.ok_or(std::io::Error::from_raw_os_error(22))
    }
}

fn a() -> Result<String, Box<dyn std::error::Error>> {
    let err = std::io::Error::from_raw_os_error;
    let mut hw = String::new();
    hw.push_str(c()?.as_str());
    hw.push(' ');
    hw.push_str(b().or_22()?.get_data().or_22()?.as_str());
    Ok(hw)
}
3 Likes

I know I can chain ok_or/ok_or_else, but the lines become so long, I don't like them anymore, so I just put them on separate lines for that reason.

The problem of mine is that I need to write ok_or/ok_or_else all the time where I have to specify a new error every single time (this you did as well, but with a bit of simplification, which is quite nice actually). So I just use an arbitrary error there to make things recognizable on the caller side. If on the other hand I have results, I don't need to specify further errors. What would be real good is to have an error which is produced by ? or a very similar short construct on an Option when its value is None.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.