Is it idiomatic Rust to accept a Result<T, E> as a function argument?

Viewed 279

Consider the following code:

fn foo(x: i32) -> Result<i32, Error> {
    //...
}

fn bar(x: Result<i32,Error>) -> Result<i32, Error> {
    //...
}

fn main() {
    let y = bar(foo(2)).unwrap();
}

Is this idiomatic, to pass the Result type around? Or should you handle the error or unwrap the result of bar() before passing the i32 directly.

2 Answers

It's quite unusual to accept a Result as an argument, except in general purpose libraries for dealing with Results.

Result has a lot of methods which help make working with it more ergonomic. For example, and_then, which chains a function call onto a previous result. Your example can be changed to:

fn foo(x: i32) -> Result<i32, Error> {
    //...
}

fn bar(x: i32) -> Result<i32, Error> {
    //...
}

fn main() {
    let y = foo(2).and_then(|value| bar(value)).unwrap();

    // or more concisely in this simple case:
    let y = foo(2).and_then(bar).unwrap();
}

I can't say I've seen a situation where it made much sense but without a clearer explanation as to why you'd do that it's difficult to help more.

Essentially, does bar have any use for an Err() input, or is it just going to pass that through directly? In the former case, then yes it might make sense for bar to take a Result -- though given you're asking the question that looks unlikely

However if bar starts with something along the lines of

fn bar(x: Result<i32, Error>) -> Result<i32, Error> {
    let y = x?;
    // work with an actual `i32` and potentially output an error as well

that is it has no actual use for an Err, then no, it's not idiomatic, the input is unnecessarily and uselessly complicated.

In this case what you'd want is:

fn bar(x: i32) -> Result<i32, Error> {
    //...
}

fn main() {
    let y = foo(2).and_then(bar).unwrap();
}
Related