7
votes

I need to store a fn(I) -> O (where I & O can be references) in a 'static struct. O needs to be a trait with an 'static generic associated type, that associated type is also stored in the struct. Neither I nor O itself get stored inside of the struct, so their lifetime shouldn't matter. But the compiler is still complaining about I not living long enough.

trait IntoState {
    type State: 'static;

    fn into_state(self) -> Self::State;
}

impl IntoState for &str {
    type State = String;

    fn into_state(self) -> Self::State {
        self.to_string()
    }
}

struct Container<F, S> {
    func: F,
    state: S,
}

impl<I, O> Container<fn(I) -> O, O::State>
where
    O: IntoState,
{
    fn new(input: I, func: fn(I) -> O) -> Self {
        // I & O lives only in the next line of code. O gets converted into
        // a `'static` (`String`), that is stored in `Container`.
        let state = func(input).into_state();
        Container { func, state }
    }
}

fn map(i: &str) -> impl '_ + IntoState {
    i
}

fn main() {
    let _ = {
        // create a temporary value
        let s = "foo".to_string();

        // the temporary actually only needs to live in `new`. It is
        // never stored in `Container`.
        Container::new(s.as_str(), map)
        // ERR:        ^ borrowed value does not live long enough
    };
    // ERR: `s` dropped here while still borrowed
}

playground

2
The problem seems to lie in the associated generic type, if i remove it (inserting String as return type) it starts to work. playground. But i it to be generic for my application logic.chpio
Should i update the title & description to better reflect the issue (not storing the fn is the problem here, but the IntoState trait/"missing" generic type) ?chpio
I would update it in a way that it is not about the static lifetime, but it is still reflecting the compiler's error message (because people will search for that misleading stuff)Peter Varo

2 Answers

5
votes

As far as I can tell, the compiler's error message is misleading, what it actually requires is an explicitly defined associated type:

fn map(i: &str) -> impl '_ + IntoState<State = String> {
    i
}

The excellent answer given to the quesion: Why does the compiler not infer the concrete type of an associated type of an impl trait return value? provides enough information on why this is actually needed.

See also Rust issue #42940 - impl-trait return type is bounded by all input type parameters, even when unnecessary

You can use a generic type parameter instead of returning an impl in which case you don't have to specify the associated type:

fn map<T: IntoState>(i: T) -> T {
    i
}
3
votes

Apparently there is still some confusion about what exactly is going on here, so I'll try to destil my comments into a short answer.

The problem here is the prototype of the function map():

fn map(i: &str) -> impl '_ + IntoState

This specifies that the return type of map() is some type implementing IntoState, with an unspecified associated type State. The return type has a lifetime parameter with the lifetime of the argument i; let's call that lifetime 'a, and the full return type T<'a>. The associated type State of this return type now is <T<'a> as IntoState>::State, which is parametrized by 'a. The compiler is currently not able to eliminate this lifetime parameter from the assoicated type, in spite of the 'static declaration in the trait definition. By explicitly specifying the associated type as String, the compiler will simply use the explicitly specified type String instead of <T<'a> as IntoState>::State, so the lifetime parameter is gone, and we don't get an error anymore.

This compiler shortcoming is discussed in this Github issue.