Lifetime of error object when using anyhow

I was trying to implement a function that separates a string by a given separator, parses resulting parts to numbers, and returns the vector of numbers.
Also, I'm trying to use anyhow for error handling.

This is what I came up with:

use anyhow::{Context, Result};
use std::{str::FromStr};

pub fn split_parse<T: FromStr>(data: &str, sep: &str) -> Result<Vec<T>>
where
    T: Sized,
    <T as FromStr>::Err: Send,
    <T as FromStr>::Err: Sync,
    <T as FromStr>::Err: std::error::Error,
    <T as FromStr>::Err: 'static,
{
    data.split(sep)
        .map(|it| it.parse::<T>().context(format!("can't parse '{}'", it)))
        .collect()
}

fn main() {
    println!("{:?}", split_parse::<i32>("100,200,300", ","));
    println!("{:?}", split_parse::<f32>("100,200,300", ","));
    println!("{:?}", split_parse::<bool>("100,200,300", ","));
}

It works as expected. But I do not understand why I had to add the 'static lifetime constraint to Err.

If I remove this constraint, the compiler gives me this error: "the associated type <T as FromStr>::Err may not live long enough."

Can somebody please explain what's going on here?

Look at the definition of the context function:

pub fn context<C>(self, context: C) -> Selfwhere
    C: Display + Send + Sync + 'static,

This is where the bound comes from.

2 Likes

Thank you for pointing me there.
This raises one more question: C is context, but in my code, we are speaking about the Err type.
And I actually don't understand what that means: for me, lifetimes are about references, but it looks like the object's ownership is transferred and the object becomes owned by Result and shares lifetime with it. So why is there a lifetime at all?

Ah, lifetimes aren't about references at all, they are about types. It so happens that the reference type is the most common types to have a lifetime associated with it. But that is not the only types with associated lifetimes.
Consider the type F of a closure, which borrows from its enclosing function. The lifetime of this closure is bound to what it borrows. Thus F also has a lifetime associated with it.
That's why a type C can have a lifetime even if it is not a reference.

1 Like

Also, as a generic parameter, C could be a reference; as an associated type, Err could be a reference. (References are types too.)

2 Likes

Everything has a lifetime in Rust. Every type, every variableā€¦

Just some types (and a few variables) have 'static lifetime.

If type have 'static lifetime it means that corresponding variable may survive till the end of program execution, if neededā€¦ but it doesn't mean it have to live that long.

Classic 'static type is a simple i8 or usize integerā€¦ but language where every integer have to live till the end of program execution would be pretty silly.

2 Likes