Lifetime errors with Generic and FnOnce

Hey so I have a minimal reproducible example of code here that would do what I want, if I could get it to compile.

// Holds values
struct Container {
    value: String,
}

// Trait that allows you to call .with() on the type with a closure that takes T
pub trait Call<T> {
    fn with<R, F>(self, f: F)
    where
        F: FnOnce(T);
}

// This should allow you to call `container.with(|s: &str| println!("{}", s))`
impl<'a> Call<&'a str> for Container {
    fn with<R, F>(self, f: F)
    where
        F: FnOnce(&'a str),
    {
        let value = self.value.as_str();
        f(value);
    }
}

I believe the issue is with the actual Impl line, as I'm saying Call<'a str>, this locks me to a specific lifetime. If I could use a for<'a> that should work but that only works in a where clause.

One option is to make the closure take a reference explicitly, and then implement the trait for str instead of &str

// Holds values
struct Container {
    value: String,
}

// Trait that allows you to call .with() on the type with a closure that takes T
pub trait Call<T: ?Sized> {
    fn with<R, F>(self, f: F)
    where
        F: FnOnce(&T);
}

// This should allow you to call `container.with(|s: &str| println!("{}", s))`
impl Call<str> for Container {
    fn with<R, F>(self, f: F)
    where
        F: FnOnce(&str),
    {
        let value = self.value.as_str();
        f(value);
    }
}

You have to add T: ?Sized to implement the trait for str but that's not a problem in this specific example. It may cause problems in your actual use case though if you're also trying to use that type parameter in a context where it needs to be Sized.

Thanks but my actual use case is a little more complicated, the actual target is not &str but a Type with a generic lifetime.

You may need to rely on a trait to emulate a generic type constructor; here's one way, perhaps not fully applicable to your use case due to the blanket implementation for T: Sized.

Here's more on the issue generally -- in short, a type parameter like T can only resolve to a single explicit type (including all lifetimes), and not a type constructor like &str or YourType<'_> (over any possible lifetime).

Using an emulated type constructor like this does also tend to wreck inference. Dropping blanket implementations for concrete ones can help at the expense of being unrestrictedly generic. I don't know of any silver bullet though.

1 Like