billy voiderer
billy voiderer

Reputation: 101

Rust - evaluate result in Option map closure

I am trying to map value inside Option and if it is missing then I want to return Error. Currently I am using approach:

pub fn unmarshall_root(opt: Option<String>) -> Result<String, io::Error> {
    let res = match opt {
        Some(s) => unmarshall_child(s)?,
        None => return Err(new_error(String::from("input must be defined"))),
    };

    Ok(res)
}

pub fn unmarshall_child(o: String) -> Result<String, io::Error> {
    Ok(o)
}

It works fine, but want something more elegant. Something like:

opt.map(|it| unmarshall_child(it)?)
    .ok_or_else(Err(new_error(String::from("input must be defined"))))

Which cannot be compiled (err message: "cannot use the ? operator in a closure that returns"). Any recommendation for more elegant approach?

EDIDTED: Thanks for all proposed approaches. I have another question, how to handle situation more elegantly when I do not to fail if value is empty:

pub fn unmarshall_root(opt: Option<String>) -> Result<Option<String>, io::Error> {
    let res = match opt {
        Some(s) => Some(unmarshall_child(s)?),
        None => None,
    };

    Ok(res)
}

Upvotes: 0

Views: 1567

Answers (2)

GManNickG
GManNickG

Reputation: 503855

Removing the unnecessary unwrapping-and-rewrapping, you'd have:

pub fn unmarshall_root(opt: Option<String>) -> Result<String, io::Error> {
    match opt {
        Some(s) => unmarshall_child(s),
        None => Err(new_error(String::from("input must be defined"))),
    }
}

This is already pretty straightforward. A common rearrangement (if you need to operate on s a bit more than once) is:

pub fn unmarshall_root(opt: Option<String>) -> Result<String, io::Error> {
    let s = if let Some(s) = opt {
        s
    } else {
        return Err(new_error(String::from("input must be defined")));
    };

    // ...

    unmarshall_child(s)
}

This gives good flexibility without over-closurifying the code.


When let-else is finalized, this can be written:

pub fn unmarshall_root(opt: Option<String>) -> Result<String, io::Error> {
    let Some(s) = opt else {
        return Err(new_error(String::from("input must be defined")));
    };

    unmarshall_child(s)
}

But if you really must one-line it, then:

pub fn unmarshall_root(opt: Option<String>) -> Result<String, io::Error> {
    opt.ok_or_else(|| new_error(String::from("input must be defined")))
        .and_then(unmarshall_child)
}

Upvotes: 0

Chayim Friedman
Chayim Friedman

Reputation: 70970

I'm not sure this is more elegant, but you can rewrite it like:

unmarshall_child(
    opt.ok_or_else(|| new_error(String::from("input must be defined")))?,
)?

Upvotes: 1

Related Questions