Search code examples
rustnomthiserror

How do I use #from to convert nom parsing errors into my thiserror error variant?


Consider this snippet:

use thiserror::error;

pub fn parse_something(input: &str) -> Result<i32, MyError> {
    let (_, number) = nom::number::complete::i32(input)?;
    Ok(number)
}

// define error
#[derive(Error, Debug, PartialEq)]
pub enum MyError {
    #[error("Parsing error in the input")]
    ParseError(#[from] nom::Err<nom::error::Error<&'static str>>),
}

It won't compile because apparently the

borrowed data escapes outside the function

I'm a bit stumped. I just want to catch all the parsing errors in a nice way and return that variant.

I could always use map_err in parse_something, but I thought the #[from] macro exists precisely so I don't have to do it manually.


Solution

  • The reason it's not working in your snippet is because the lifetime of input is elided, so the Rust compiler assigns a distinct lifetime (e.g. 'a) to input. Your function (after expanding the lifetime) looks like this to the compiler:

    pub fn parse_something<'a>(input: &'a str) -> Result<i32, MyError>
    

    The lifetime 'a, unless manually specified by you, is assumed to be shorter than 'static. The error is expecting the lifetime of its &str to be 'static, so the elided lifetime is too short, and the compiler complains.

    As you probably found if you tried giving the error any lifetime shorter than 'static, thiserror doesn't let you give references any non-static lifetimes because std::error::Error requires the source is dyn Error + 'static. So, non-static lifetimes are out of the window as well.

    To fix this, you need to make input have a 'static lifetime. This snippet below compiles:

    use thiserror::Error;
    
    pub fn parse_something(input: &'static str) -> Result<i32, MyError> {
    
        let (_, number) = nom::character::complete::i32(input)?;
        Ok(number)
    }
    
    // define error
    #[derive(Error, Debug, PartialEq)]
    pub enum MyError {
        #[error("Parsing error in the input")]
        ParseError(#[from] nom::Err<nom::error::Error<&'static str>>),
    }
    

    It may not be possible to have input be 'static, though. In cases where input is not static, you will need to map the error into a type that contains a String. It could look something like this:

    use thiserror::Error;
    
    pub fn parse_something(input: &str) -> Result<i32, MyError> {
    
        let (_, number) = nom::character::complete::i32(input)
            .map_err(|e: nom::Err<nom::error::Error<&str>>| e.map_input(|input| input.to_string()))?;
        Ok(number)
    }
    
    // define error
    #[derive(Error, Debug, PartialEq)]
    pub enum MyError {
        #[error("Parsing error in the input")]
        ParseError(#[from] nom::Err<nom::error::Error<String>>),
    }
    

    You can find some more information about difficulty dealing with nom's errors in this post on the Rust forums.