Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Store reference of struct in other struct

Tags:

rust

I have two structs. App and Item.

What I want to achieve is to store an Item in the items vector of the App struct by passing a mutable reference to the Items constructor.

pub struct App<'a> {
    items: Vec<&'a Item>
}

impl<'a> App<'a> {
    pub fn new() -> App<'a> {
        App { items: Vec::new() }
    }
    pub fn register_item(&mut self, item: &'a Item) {
        self.items.push(item);
    }
}

pub struct Item;

impl Item {
    pub fn new(app: &mut App) -> Item {
        let item = Item;
        app.register_item(&item);
        item
    }
}

fn main() {
    let mut app = App::new();

    let item = Item::new(&mut app);;
}

The code thows the following error:

test.rs:8:28: 8:32 error: `item` does not live long enough
test.rs:8         app.register_item(&item);

Is there any way to do this?

like image 348
lschmierer Avatar asked May 29 '15 20:05

lschmierer


2 Answers

While Rc might be correct for your use case, it's good to understand why you are getting the error you are. Please read Why can't I store a value and a reference to that value in the same struct? as it has a much more in-depth discussion about why your code cannot work as-is. A simplified explanation follows.

Let's look at your constructor:

fn new(app: &mut App) -> Item {
    let item = Item;
    app.register_item(&item);
    item
}

Here, we create a new Item on the stack at some address. Let's pretend that address is 0x1000. We then take the address of item (0x1000) and store it into the Vec inside the App. We then return item to the calling function, which resides in a different stack frame. That means that the address of item will change, which means that 0x1000 is no longer guaranteed to point to a valid Item! This is how Rust prevents you from making whole classes of memory errors!

I'd say that you'd normally see this written as:

fn main() {
    let item = Item;
    let mut app = App::new();
    app.register_item(&item);
}

This will have the same problem if you tried to return app or item from this function, as the address will change.


If you have a straight-forward tree structure, I'd advocate for simply letting the parent nodes own the children:

struct App {
    items: Vec<Item>
}

impl App {
    fn new() -> App {
        App { items: Vec::new() }
    }

    fn register_item(&mut self, item: Item) {
        self.items.push(item);
    }
}

pub struct Item;

fn main() {
    let mut app = App::new();
    app.register_item(Item);
}
like image 184
Shepmaster Avatar answered Sep 20 '22 23:09

Shepmaster


Simple solution is to use Rc.

use std::rc::Rc;

pub struct Item;

impl Item {

    pub fn new(app: &mut App) -> Rc<Item> {
        let item = Rc::new(Item);
        app.register_item(item.clone());
        item
    }

}

pub struct App {
    items: Vec<Rc<Item>>
}

...
like image 43
lschmierer Avatar answered Sep 20 '22 23:09

lschmierer