Lifetime syntax problems

I see why I need to use life times in this code, I have noted it in the comments. But I cannot find a syntax for them that will work.

struct Foo {
    x: usize,
}

impl Foo {

    // [a] Using `Self` here.  Could use `Foo`.  Is it the same thing                                                                                                    
    // in this context                                                                                                                                                   
    fn new(i:usize, holder: &mut Vec<& Self>) -> Self{
        // Creating a `Foo` object, placing a reference to it in                                                                                                         
        // `holder`.  The reference, and Foo, need to live as long as                                                                                                    
        // `holder`                                                                                                                                                      
        let ret = Foo {
            x:i,
        };
        holder.push(&ret);
        ret
    }
}

fn main() {
    // This is the scope where all the data must live.  How?                                                                                                             
    let mut held:Vec<&Foo> = Vec::new();
    for i in 0..10 {
        // This `Foo` objects need to live as long as the `held` vec                                                                                                     
        Foo::new(i, &mut held);
    }
    // Do something with `held`                                                                                                                                          
}

I know this is weird code. It is a small part of a larger project. I extracted the part that illustrates what I cannot make work.

I have not included all the different things I tried. I find myself thrashing around and nothing compiles.

The compiler says....

cargo build
   Compiling lifetimes v0.1.0 (/home/patch/sandbox/lifetimes)
error[E0597]: `ret` does not live long enough
  --> src/main.rs:16:14
   |
9  |     fn new(i:usize, holder: &mut Vec<& Self>) -> Self{
   |                                      - let's call the lifetime of this reference `'1`
...
16 |     holder.push(&ret);
   |     ------------^^^^-
   |     |           |
   |     |           borrowed value does not live long enough
   |     argument requires that `ret` is borrowed for `'1`
17 |     ret
18 |     }
   |     - `ret` dropped here while still borrowed

error[E0505]: cannot move out of `ret` because it is borrowed
  --> src/main.rs:17:2
   |
9  |     fn new(i:usize, holder: &mut Vec<& Self>) -> Self{
   |                                      - let's call the lifetime of this reference `'1`
...
16 |     holder.push(&ret);
   |     -----------------
   |     |           |
   |     |           borrow of `ret` occurs here
   |     argument requires that `ret` is borrowed for `'1`
17 |     ret
   |     ^^^ move out of `ret` occurs here

error: aborting due to 2 previous errors

It is telling me (I think) that the Foo instance must live as long as the vector the reference is placed in. That I know. How do I tell the compiler?

This is not possible. Moving a value breaks any references to it, since the reference would still point at its old location. However the return of ret from new is a move of a value that holder has a reference to.

2 Likes

Sharing a reference does not tell to the compiler « let live the referenced object enough » (as smart pointers do) but tell « check that the referenced objet will live enough ».
So you can’t do what you expect.

Depending of what you want, you can store the object in the holder and returns a reference on it (ok, it is a little bit weird and it is definitively not a classical new method).

struct Foo {
    x: usize,
}

impl Foo {

    // [a] Using `Self` here.  Could use `Foo`.  Is it the same thing                                                                                                    
    // in this context                                                                                                                                                   
    fn new<'a>(i:usize, holder: &'a mut Vec<Self>) -> &'a Self
    {
        // Creating a `Foo` object, placing **it** in                                                                                                         
        // `holder`.  The returned reference will leave as long as the holder                                                                                                                               
        let ret = Foo {
            x:i,
        };
        holder.push(ret);
        holder.last().unwrap()
    }
}

fn main() {
    // The holder stores objects, not references                                                                                                             
    let mut held:Vec<Foo> = Vec::new();
    for i in 0..10 {
        // This `Foo` objects need to live as long as the `held` vec                                                                                                     
        Foo::new(i, &mut held);
    }
    // Do something with `held`                                                                                                                                          
}
1 Like

I get it.

I was still not used to thinking ownership.

What I am looking for is Rc I want to store my objects on the heap and have a pointer to it in the vector

use std::rc::Rc;

struct Foo {
    x: usize,
}

impl<'a> Foo {
    fn new(i:usize, holder: &'a mut Vec<Rc<Self>>) -> Rc<Self>{
        let ret = Rc::new(Foo {
            x:i,
        });
        holder.push(ret.clone());
        ret
    }
    fn test(&self) {
        println!("{}", self.x);
    }
}

fn main() {
    let mut held:Vec<Rc<Foo>> = Vec::new();
    for i in 0..10 {
        Foo::new(i, &mut held);
    }
    for rcf in held {
        rcf.as_ref().test();
    }
}

Compiles.
Nothing to do with life times.

1 Like

Ok, so it is a more common situation.

I suggest to put the Rc wrapping out of the new : so the new is more conventional and allows the end user to wrap Foo object or not . And you could also use the collect method of Vec to initialize, resize and fill the holder.

So you will have:

use std::rc::Rc;

struct Foo {
    x: usize,
}

impl Foo {
    fn new(i:usize) -> Self {
        Self {
            x:i,
        }
    }
    fn test(&self) {
        println!("{}", self.x);
    }
}

fn main()
{
    let held: Vec<Rc<Foo>> = (0..10).into_iter()
        .map(|i| Rc::new(Foo::new(i)))
        .collect();

    for rcf in held {
        rcf.as_ref().test();
    }
}

Moreover, the allocated space of Vec (i.e. its capacity) is then exactly 10 and the allocation is made only one time.
In the previous implementation, the loop grows the capacity many times (doubles it each time) so you obtain a capacity of 16 for the 10 objects.

Iterators have very useful underlying behaviours that loops haven’t.

But ok, I agree this is not the initial question.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.