Midnight Exigent
Midnight Exigent

Reputation: 625

Should I ditch using `and_then` and always use the `?` operator?

I want to write something like this, but it won't compile due to a mismatch between types:

fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    let val = std::env::args()
        .nth(1)
        .ok_or("1 arg is expected")
        .and_then(std::fs::File::open)
        .and_then(serde_yaml::from_reader)?;
}

Because adding a map_err to every closure seems sluggish and 'boiler platy', I replace it with something like:

fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    let val = serde_yaml::from_reader(std::fs::File::open(
        std::env::args().nth(1).ok_or("1 arg is expected")?,
    )?)?;
}

The first one feels more natural and reads like English while the second feels sort of backwards.

Should I ditch the and_then and always use the ? operator?

If not, is there a way to make result combinator as smooth as the ? operator?

Upvotes: 1

Views: 1163

Answers (2)

LMJWILL
LMJWILL

Reputation: 171

The reason why your first expression doesn't work is because the error types are not matching. To be more specific,

let val = std::env::args()
        .nth(1)
        .ok_or("1 arg is expected")
        .and_then(std::fs::File::open)
        .and_then(serde_yaml::from_reader)?;

std::env::args().nth(1) returns a Option<T>. If you look at the ok_or signature, which is pub fn ok_or<E>(self, err: E) -> Result<T, E>. This means for your case, ok_or("1 arg is expected") your return type is Result<T, &str>. So your error type here is &str because in ok_or, you pass a string slice as an Error type.

If you take a look at the and_then method, the signature is pub fn and_then<U, F>(self, op: F) -> Result<U, E> where F: FnOnce(T) -> Result<U, E>, basically this means the Function you passed in and_then should have the same error type as the original Result, which is a &str. This is same as the line .and_then(serde_yaml::from_reader), the error types needs to be consistent for all the "chaining" and_then functions.

If you really want, you can do the following way to make your code compile. Or you can create a uniform error so no mismatch error types. Then you can use the ok_or(...).and_then(...).and_then(...) kind of writing. You just need to match the error types.

working example

fn custom_fs_open(path: String) -> Result<String, &'static str>{
    // actual logics to open fs
    Ok(String::from("abc"))
}


fn custom_serde_yaml(buf: String) -> Result<String, &'static str>{
    // actual logics to do serde convertion
    Ok(String::from("cde"))
}


fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    let val = std::env::args()
            .nth(1)
            .ok_or("1 arg is expected")
            .and_then(custom_fs_open)
            .and_then(custom_serde_yaml)?;
            
    Ok(())
}

Upvotes: 1

Masklinn
Masklinn

Reputation: 42302

Should I ditch the and_then and always use the ? operator ?

That’s a personal judgement and only you can answer it.

Is there a way to make result combinator as smooth as the ? operator ?

Frankly speaking no. ? performs conversions « implicitly » (not really implicitly since it’s very much part of its job, but the conversion doesn’t have to be invoked separately, maybe « tersely »?), and_then does not. That means when using and_then, you have to perform these conversions yourself. That seems logical.

You might be able to build a convenience macro for this tho. Or maybe add an extension method or wrapper type which can perform those conversions under the cover.

Upvotes: 0

Related Questions