0
use std::collections::{HashMap, HashSet};

pub struct P<'a> {
    x: &'a str
}

pub struct Foo<'a, T> {
    callbacks: Vec<Box<'a + FnMut(&T)>>
}

impl<'a, T> Foo<'a, T>{
    pub fn foo(&mut self, payload: T) {
    }

}

pub struct Foo2<'a> {
    callbacks: Vec<Box<'a + FnMut(&P)>>
}

impl<'a> Foo2<'a>{
    pub fn foo(&mut self, payload: P) {
    }

}


struct Bar<'a, 'b> {
    x: Foo<'a, P<'b>>,
    y: Foo2<'a>,
    data: HashMap<String, String>
}


impl<'a, 'b> Bar<'a, 'b> {
    // fn test(&mut self) {
    //     // Cannot infer an appropriate lifetime.
    //     match self.data.get("foo") {
    //         Some(x) => {
    //             let p = P {x};
    //             self.x.foo(p);
    //         },
    //         None => {}
    //     }
    // }

    fn test2(&mut self) {
        match self.data.get("foo") {
            Some(x) => {
                let p = P {x};
                self.y.foo(p);
            },
            None => {}
        }
    }

}

Playground. I'm using rustc 1.19.0-nightly.

Why does test2 work but test does not? How can I correctly make the generic struct Foo?

I do not think this example involves Why can't I store a value and a reference to that value in the same struct? and is not a duplicate.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
colinfang
  • 20,909
  • 19
  • 90
  • 173

1 Answers1

2

What is 'a, what is 'b?

If we isolate the failing case (note that I introduced a lifetime for self to make it easier):

pub struct P<'a> {
    x: &'a str
}

pub struct Foo<'a, T> {
    callbacks: Vec<Box<'a + FnMut(&T)>>
}

impl<'a, T> Foo<'a, T>{
    pub fn foo(&mut self, payload: T) {
    }
}

struct Bar<'a, 'b> {
    x: Foo<'a, P<'b>>,
    data: HashMap<String, String>
}


impl<'a, 'b> Bar<'a, 'b> {
    fn test<'c>(&'c mut self) {
        // Cannot infer an appropriate lifetime.
        match self.data.get("foo") {
            Some(x) => {
                let p = P {x};
                self.x.foo(p);
            },
            None => {}
        }
    }
}

The problem here is that when you instantiate Bar, you fix what 'a and 'b is.

Specifically, this lifetime is NOT 'c, which is totally unrelated.

The compiler sees:

  • the argument to self.x.foo must have type P<'b>,
  • it has type P<'unknown>, where 'unknown is any lifetime less than 'c,
  • it has type P<'unknown>, so 'unknown must be greater than 'b,
  • 'b and 'c are unrelated.

and has no idea what 'unknown should be.


A potential solution is to avoid fixing 'b:

pub struct Foo<'a> {
    callbacks: Vec<Box<'a + FnMut(&P)>>,
}

impl<'a> Foo<'a> {
    pub fn foo(&mut self, payload: P) {}
}

struct Bar<'a> {
    x: Foo<'a>,
    data: HashMap<String, String>,
}

Note: and at this point, 'a seems superfluous as well.

However this requires us fixing T as well, because when using a type parameter (as we had with Foo<'a, T>), then we need to fully specify the type and thus name the lifetime that P will contain.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I kinda guessed the potential solution as `Foo2` in my example. However, if I have multiple specific types of `T`, do I have to define like `Foo3, Foo4 ...`. Can I not reuse the code from `Foo`? – colinfang May 24 '17 at 16:26
  • @colinfang: I am not exactly sure what you mean... note however that the whole difficulty comes from `P` having a reference inside, it may be worth exploring making that an owned value and bear the cost of having a copy (and possibly use `Rc` to lessen the cost of the copy). – Matthieu M. May 25 '17 at 06:42