#Dealing with disparate error types

1 messages · Page 1 of 1 (latest)

austere badge
#

Description

I have an error propagation question I just can't wrap my brain around. I'm trying to write

let iface = 
    std::env::var("TOKEN") // Result<String, VarError>
    .and_then(|token| iface::Iface::new(&token)); // Result<iface::Iface, iface::Error>
```, basically trying to turn `Result<T, E>` into `Result<U, F>` in one go. The compiler diagnostic is kind of cryptic:

mismatched types
expected enum Result<_, VarError>
found enum Result<iface::Iface, iface::Error>

What can I do here to propagate mismatched error types (really `Result` types) around? I know could separate things out - 
```rust
let token = std::env::var("TOKEN")?;
let iface = iface::Iface::new(&token)?;

but that doesn't really give me what I want. Constructing the Iface is common to some other pieces of code, and some of those operations are still meaningful even if I can't construct an Iface. So I'd like to defer unwrapping the Result until later when certain operations must fail (thus the ? operator is a no-go).

Update

I think

#[derive(Debug, thiserror::Error)]
enum ProgramError {
    VarError(#[from] VarError),
    IfaceError(#[from] IfaceError)
}

let iface = 
    std::env::var("TOKEN")
    .map_err(Into::<ProgramError>::into)
    .and_then(|token| 
        Iface::new(&token)
        .map_err(Into::<ProgramError>::into)
    );

is a better expression of what I'm trying to do (and it works!) it just feels very cumbersome. Is there a better way to consolidate nested errors?

lofty yew
#

You've got two different failure cases you want to store in the variable iface, so you must convert them into the same type, or change how you store the error

#

But you can maybe improve the code that gets there:

#

fewer type annotations, take 1:

let iface: Result<Iface, ProgramError> = 
    std::env::var("TOKEN")
    .map_err(Into::into)
    .and_then(|token| 
        Iface::new(&token).map_err(Into::into)
    );
#

fewer type annotations, take 2:

let iface = 
    std::env::var("TOKEN")
    .map_err(ProgramError::from)
    .and_then(|token| 
        Iface::new(&token).map_err(ProgramError::from)
    );
#

Consider using a match instead of and_then:

let iface = match std::env::var("TOKEN") {
    Ok(token) => Iface::new(&token).map_err(ProgramError::from),
    Err(e) => ProgramError::from(e),
};
#

My personal heuristic is that any time I've written two method calls to handle the same Result (here .map_err and .and_then) it's probably time to just use a match instead. The result will be clearer, because a match spells out what happens in each case, whereas to follow a method chain you have to track what the value is at each step.

austere badge
#

Aha, brilliant

#

This is exactly the sort of analysis I was hoping for 🙂

#

Thank you - that makes things much more clear!

whole sierra
#

another option ```rs
let iface = (|| {
let token = std::env::var("TOKEN")?;
Ok(iface::Iface::new(&token)?)
})();