Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Impl trait with generic associated type in return position causes lifetime error

Tags:

rust

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

like image 311
chpio Avatar asked Jun 17 '19 17:06

chpio


Video Answer


2 Answers

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
}
like image 186
Peter Varo Avatar answered Nov 05 '22 03:11

Peter Varo


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.

like image 21
Sven Marnach Avatar answered Nov 05 '22 02:11

Sven Marnach