Values referenced by collection and not living long enough


#1

Hi,

My problem is the following. I have created custom types and I need to handle them using some sort of a collection, let’s say a simple vector for this example. I also want my vector to store references to my objects. In other words, I don’t want my vector to own my type instances, only borrow them. I also need to be able to create and probably destroy type instances during the lifetime of my vector. I am encountering the issue of my referenced objects not living long enough just because a referenced resource which was created after my vector is being dropped before the vector itself, hence causing and invalid reference and a compiler error. Here’s the code:


struct SomeType {

	num: i32,
}


fn main() {


    let mut v: Vec<&SomeType> = Vec::new();

    let t1 = SomeType { num: 5 };

    v.push(&t1);

    println!("{:?}", v[0].num);

    //Hoped something like the following would work:
    //v.clear();
    //drop(v);
}

Of course, if I declare and define t1 before v, the problem will be solved. However, in my real world application scenario, this is not possible as type instances which represent objects will have to be created and destroyed while the collection is still alive. I cannot reasonably create all my objects before I create the collection which will be referencing them.

How is it possible to have the vector hold references to type instances or values which are being created and destroyed while it is still alive? I tried to manually drop the vector, or clearing it, etc. but then I learned that borrowing is still lexical in Rust and that I can only play with scopes i.e. {}, which doesn’t seem useful in my case.

Any ideas?

Thanks


#2

Perhaps using Rc<Box<SomeType>> instead of &SomeType? Once you destroy SomeType (well, decrement the ref count if using Rc) elsewhere in your code, the sole ref will be from the vector. If you then remove the Rc from the vector (or clear the vector), the ref count will drop to 0 and they’ll get truly destroyed.


#3

The borrow checker is pretty high-level. It sees a value of type Vec<&Sometype> and reasons that it must contain values of type &'a Sometype, where 'a is the lifetime of the Vec. There’s no way to inform it that calling .clear() on v turns it into a value that doesn’t contain any references.

You need dynamic lifetime handling to manage this. The simplest way is to make the Vec own the values, either as a unique value with Box<Sometype> or shared ownership with Rc<Sometype>.


#4

@minno, your post made me realize that I shouldn’t have put Box in my last example - SomeType is a concrete struct in this particular example, not some trait object, so Rc<SomeType> is sufficient. Just wanted to correct this to prevent confusion.


#5

Actually Rc<SomeTrait> does work. Example.


#6

Oh sorry, you’re using trait here. Right, I guess so long as only refs are used it’s fine, which is the case with Rc.


#7

Thank you for your answers. Unfortunately, using Rc actually transfers ownership to the vector and I can’t use the object anymore.

@minno mentioned dynamic lifetime handling but AFAIK this is not supported yet.

Is there any way I can store references to my types in my vector while still being able to use my type objects?

In my real application, I have to reference trait objects.

Thank you guys.


#8

One more remark: I have to use Arc<Mutex> in my real application because I need mutable multi-threaded access.

And using Arc<Mutex<MyType>> actually transferred ownership and generated compiler errors related to lifetime when I tried to access my type; whereas using Arc<Mutex<&MyType>> generated the original ‘type doesn’t live long enough’ message.

Thanks.


#9

For example if I modify @minno’s example to try to use the type instances after adding them to the vector, I get the following error. Again, I need to be able to add references to my type instances to the vector and still be able to use those instances from outside the vector. I also need mutable access to my types (so I am using Arc/Mutex instead of Rc in my real application).

use std::rc::Rc;

trait Thingy {
    fn say(&self);
}

struct Thing1(&'static str);
impl Thingy for Thing1 {
    fn say(&self) {
        println!("{}", self.0);
    }
}

struct Thing2(i32);
impl Thingy for Thing2 {
    fn say(&self) {
        println!("{}", self.0);
    }
}

fn main() {

    let thing1 = Thing1("hi");
    let thing2 = Thing2(3);

    let things: Vec<Rc<Thingy>> = vec![
        Rc::new(thing1),
        Rc::new(thing2)
    ];
    
    for thing in things {
        thing.say(); 
    }
    
    println!("{:?}", thing1.say());
}

rustc 1.14.0 (e8a012324 2016-12-16)
error[E0382]: use of moved value: thing1
–> :35:22
|
27 | Rc::new(thing1),
| ------ value moved here

35 | println!("{:?}", thing1.say());
| ^^^^^^ value used here after move
|
= note: move occurs because thing1 has type Thing1, which does not implement the Copy trait

error: aborting due to previous error


#10

So you’d have to use your Thingy’s via Rc everywhere. In the example above you create two owned values, thing1 and thing2, but you’d need to create them as Rc and then add clone()'s to the vec. Then you have shared ownership by the vec and by whatever other code has an Rc to it.


#11

Exactly. This is what I finally figured out. I eventually had to wrap my type instances in Arc<Mutex<T>> objects and store them as such in my collection which is actually a custom type with its own fields and methods; and then whenever I need to use an object, I call a method which returns a cloned copy of my Arc<Mutex<T>> element.

Thanks a lot guys. This is a wonderful community.