0

Following this answer, how does one flatten results when they have different Error types.

Here's what I'm trying to achieve:

let length: usize = std::env::var("LENGTH").and_then(|id| id.parse::<usize>()).unwrap_or(8);

However the compiler throws the following error:

error[E0308]: mismatched types
expected enum VarError, found struct ParseIntError

at id.parse::<usize>().

Is there a way to do this or am I stuck with the uglier alternative:

let length: usize = std::env::var("LENGTH").unwrap_or_else(|_| 8.to_string()).parse().unwrap_or(8);
TheChubbyPanda
  • 1,721
  • 2
  • 16
  • 37
  • 2
    You could use [`ok`](https://doc.rust-lang.org/std/result/enum.Result.html#method.ok) to transform the `Result` into an `Option` (dropping `Err` and using a `None` instead). It doesn't really answer your question, but it prevents the `8.to_string().parse()`. – Jeremy Meadows Jul 11 '22 at 17:10
  • @JeremyMeadows I think that does answer the question. – cdhowie Jul 11 '22 at 18:16

2 Answers2

2

For your case @JeremyMeadows answer is probably the best.

But in general, if you want to keep the error, you could do something like:

let length: usize = std::env::var("LENGTH")
    .map_err(Box::<dyn std::error::Error>::from)
    .and_then(|id| id.parse::<usize>().map_err(Into::into))
    .unwrap_or(8);

Or even more generic, which is probably how I would do it in my own projects:

use std::{error::Error, str::FromStr};

fn main() {
    fn parse_from_env<T>(key: &str) -> Result<T, Box<dyn Error>>
    where
        T: FromStr,
        <T as FromStr>::Err: Into<Box<dyn std::error::Error>>,
    {
        std::env::var(key)?.parse().map_err(Into::into)
    }

    let length: usize = parse_from_env("LENGTH").unwrap_or(8);
}

Or, if my project was more professional:

use std::{error::Error, str::FromStr};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyErrors {
    #[error("unable to read environment variable")]
    EnvNotValid(#[source] Box<dyn Error>),
}

fn main() {
    fn parse_from_env<T>(key: &str) -> Result<T, MyErrors>
    where
        T: FromStr,
        <T as FromStr>::Err: Into<Box<dyn std::error::Error>>,
    {
        std::env::var(key)
            .map_err(|e| MyErrors::EnvNotValid(e.into()))?
            .parse()
            .map_err(|e: <T as FromStr>::Err| MyErrors::EnvNotValid(e.into()))
    }

    let length: usize = parse_from_env("LENGTH").unwrap_or(8);
}
Finomnis
  • 18,094
  • 1
  • 20
  • 27
1

You can use Result::ok() to transform the Result into an Option, discarding the Err and replacing it with a None:

fn main() {
    let length: usize = std::env::var("LENGTH")
        .ok()
        .and_then(|id| id.parse::<usize>().ok())
        .unwrap_or(8);
    println!("{length}");
}
Jeremy Meadows
  • 2,314
  • 1
  • 6
  • 22