1

I have a method with a parameter of type Result. I like chaining a lot, so I use and_then on the parameter. At some point, I want to return the whole method from inside the and_then conditionally (so there is a chance another and_then method can be called):

enum Good {
    Ok,
    Good,
    VeryGood,
}
enum Pizza {
    Tomato,
    Pineapple,
}
enum Burger {
    Cow,
}

enum Food {
    Pizza(Pizza),
    Burger(Burger),
}

fn main() {}

fn s(r: Result<Good, ()>) -> Result<Food, ()> {
    r.and_then(|o| {
        match o {
            // Should be called in the next and_then block
            Good::Ok => Ok(Pizza::Tomato),
            // Should be called in the next and_then block
            Good::Good => Ok(Pizza::Pineapple),
            Good::VeryGood => {
                // I am done. Don't call the next and_then block, but rather return the whole value to the caller.
                return Ok(Food::Burger(Burger::Cow));
            }
        }
    })
    .and_then(|p: Pizza| {
        // At this point, the closure input value should be pizza, because that's the only returned value
        Ok(Food::Pizza(p))
    })
}

Playground

I get all kinds of compiler errors:

error[E0308]: mismatched types
  --> src/main.rs:30:27
   |
30 |                 return Ok(Food::Burger(Burger::Cow));
   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Pizza`, found enum `Food`
   |
   = note: expected type `Pizza`
              found type `Food`

I hope there is a way to make it compile. I could break down the method and get rid of the and_then, but maybe there is a way with and_then.

In my real code, I have more and_then's which maps types to other types, error mapping etc, so this is a simplified reproduction path of the problem I am facing.

This is copy-pasted code from my codebase, showing the multiple and_then. I am mapping errors from external libraries into my own error type, so errors can be returned automatically if there are any. I want to keep changing the type that I got from the and_then so I can eventually get type User (although it is currently not working). An option is to not chain the blocks and create separate values, but I was hoping I could directly return values to the caller inside a closure.

db_session
    .query_with_values(query, values)
    .map_err(|e| {
        error!("{:?}", e);
        TechnicalServerError::SERVER_RETRY
    })
    .and_then(|f| {
        f.get_body().map_err(|e| {
            error!("{:?}", e);
            TechnicalServerError::SERVER_RETRY
        })
    })
    .and_then(|b| b.into_rows().ok_or(TechnicalServerError::SERVER_RETRY))
    .and_then(|mut c| {
        if let Some(row) = c.pop() {
            User::try_from_row(row).map_err(|e| {
                error!("{:?}", e);
                TechnicalServerError::SERVER_INVALID
            })
        } else {
            return Ok(Login::Other(LoginResult::UNKNOWN_USER));
        }
    })
    .and_then(|u| {
        // 'u' should be of type 'user' at this point
        // some user code here...
    })
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
J. Doe
  • 12,159
  • 9
  • 60
  • 114
  • The code you posted was not compiling. Could you edit it to show us the workflow better? Also, why are you returning early? Is there a specific reason? To me, this code would be better served using `map` with a single return point. – Daniel Fath Jan 01 '20 at 13:07
  • 1
    @DanielFath see my edit – J. Doe Jan 01 '20 at 16:15
  • It looks like your question might be answered by the answers of [Is there any way to return from a function from inside a closure?](https://stackoverflow.com/q/52027634/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Jan 02 '20 at 01:57
  • You should also read [How do you define custom `Error` types in Rust?](https://stackoverflow.com/q/42584368/155423), which discusses how to make `?` automatically wrap underlying errors. – Shepmaster Jan 02 '20 at 01:58

2 Answers2

1

Not a clean one. How about breaking out to ? instead?

fn s(r: Result<Good, ()>) -> Result<Food, ()> {
    let p = match r? {
        // Should be called in the next and_then block
        Good::Ok => Pizza::Tomato,
        // Should be called in the next and_then block
        Good::Good => Pizza::Pineapple,
        Good::VeryGood => {
            return Ok(Food::Burger(Burger::Cow));
        },
    };

    Ok(Food::Pizza(p))
}
Ry-
  • 218,210
  • 55
  • 464
  • 476
  • That would work indeed in the code I provided :) However, in my 'real' code, I have more and_then's and they are mapping types and errors, this way wouldn't be sufficient sadly :( Thanks for the answer anyway, I will update my question to make it more clear – J. Doe Dec 31 '19 at 20:00
  • @J.Doe: Can you create a more realistic example/show the true code, then? You can’t use `return` like that, so any alternative will probably need more context. (Also, is this targeting stable Rust or are `#![feature]`s okay?) – Ry- Dec 31 '19 at 20:04
  • Well to make it simple, I am chaining multiple and_then's and sometimes, I just want to say (inside a and_then block): ok, just return this value to the caller if something is true, else just call the next and_then. This is stable rust. To wrap it up: a return statement inside a and_then block will not actually return the function but maps the given type to the returning value, making it available in the next and_then block? – J. Doe Dec 31 '19 at 20:15
  • @J.Doe: `return` returns from the closure being passed as an argument to `and_then`, yeah. – Ry- Dec 31 '19 at 20:17
  • @J.Doe could you change example to have multiple `and_then`? – Daniel Fath Jan 01 '20 at 13:01
1

Looking at your edited example, I've created an approximate example:

// ...
    .and_then(|b| b.into_rows().ok_or(TechnicalServerError::SERVER_RETRY))
    .and_then(|mut c| {
        if let Some(row) = c.pop() {
            User::try_from_row(row).map_err(|e| {
                error!("{:?}", e);
                TechnicalServerError::SERVER_INVALID
            })
        } else {
            return Ok(Login::Other(LoginResult::UNKNOWN_USER))
        }
    }).and_then(|u| {
        // 'u' should be of type 'user' at this point
        // some user code here...

I think there is a basic misunderstanding about what and_then does.

  • and_then takes a Result<T, E>, and a function that converts T into Result<U, E> (i.e. FnOnce(T) -> Result<U, E>. Use this when you want to manipulate both the Ok(val) and the Error, for example you want to change remap the error and do some value processing.

  • map takes a Result<T, E> and a function that converts T into another value U (i.e. FnOnce(T) -> U). Use this when you want to change Ok(val) without influencing the Error part.

You say you want to change the user, and that's possible like this:

db_session
    .query_with_values(true)
    .map_err(|e| {
        println!("{:?}", e);
        MyError::ServerRetry
    })
    .and_then(|f| f.get_body(true).map_err(|e2|{
        println!("{:?}", e2);
        MyError::ServerRetry
    }))
    .and_then(|b| b.into_rows(true).ok_or(MyError::ServerRetry))
    .and_then(|mut c|{
        if let Some(row) = c.pop() {
            User::from_row(row).map_err(|e3| {
                println!("{:?}", e3);
                MyError::ServerRetry
            })
        } else {
            return Ok(User{ name: "X".to_string()})
        }
    })
    .map(|mut user| {
        user.name = "CHANGED".to_string();
        user
    });

Rust playground.

However, as you can see the value of query will always be Result<User, Error> the only way to operate on the value directly is to unwrap but that panics if it encounters an error. Alternatively you can use if let to get the value without panicking.

Daniel Fath
  • 16,453
  • 7
  • 47
  • 82