ampron
ampron

Reputation: 3646

Specify lifetime of a reference to a return argument for a member variable in an associated function

I would like to have a CatMaker struct that can create a Cat, but stores a reference to it internally so that it can later make calls to that Cat (maybe have a CatMaker.get_child_color(&self) function). I believe this implies that the CatMaker cannot outlive the Cat (otherwise it would try to deference and the Cat wouldn't be there) and the compiler seems to agree.

Here is a code example of what I would like to do:

pub enum CoatColor {
    Black,
    Tabby,
}

pub struct Cat {
    color: CoatColor,
}

pub struct CatMaker<'a> {
    child: Option<&'a Cat>,
}

impl<'a> CatMaker<'a> {
    pub fn new() -> CatMaker<'a> {
        CatMaker{ child: None }
    }

    pub fn make_cat(&mut self, color: CoatColor) -> Cat {
        let new_cat = Cat{ color: color };
        self.child = Some(&new_cat); // commenting out this line will allow it to compile
        new_cat
    }
}

fn main() {
    let mut my_cat_maker = CatMaker::new();
    let mut my_cat = my_cat_maker.make_cat(CoatColor::Black);
    my_cat.color = CoatColor::Tabby;
}

I'm having trouble figuring out how to specify the lifetime of self.child in CatMaker.make_cat.

Upvotes: 3

Views: 280

Answers (1)

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65782

You can have CatMaker own the Cat, and make_cat return a reference to the Cat, rather than returning the Cat itself.

pub enum CoatColor {
    Black,
    Tabby,
}

pub struct Cat {
    color: CoatColor,
}

pub struct CatMaker {
    child: Option<Cat>,
}

impl CatMaker {
    pub fn new() -> CatMaker {
        CatMaker { child: None }
    }

    pub fn make_cat(&mut self, color: CoatColor) -> &mut Cat {
        let new_cat = Cat { color: color };
        self.child = Some(new_cat);
        self.child.as_mut().unwrap()
    }
}

fn main() {
    let mut my_cat_maker = CatMaker::new();
    let mut my_cat = my_cat_maker.make_cat(CoatColor::Black);
    my_cat.color = CoatColor::Tabby;
}

However, this has a big restriction: you can't use my_cat_maker at all as long as you keep the result of make_cat around – here, it's stored in my_cat, so you can't use my_cat_maker until my_cat goes out of scope. That's because my_cat keeps a mutable borrow on my_cat_maker, and Rust doesn't allow two mutable borrows to be usable on the same object at the same time.

If this restriction is not acceptable to you, you'll need to use another tool to manage the lifetime of the Cat for you. Such a tool is Rc, which is a reference-counted reference to an object. If you also need to be able to mutate the Cat, you'll need to couple Rc with RefCell, which allows mutating the object in an Rc.

use std::cell::RefCell;
use std::rc::Rc;

pub enum CoatColor {
    Black,
    Tabby,
}

pub struct Cat {
    color: CoatColor,
}

pub struct CatMaker {
    child: Option<Rc<RefCell<Cat>>>,
}

impl CatMaker {
    pub fn new() -> CatMaker {
        CatMaker { child: None }
    }

    pub fn make_cat(&mut self, color: CoatColor) -> Rc<RefCell<Cat>> {
        let new_cat = Rc::new(RefCell::new(Cat { color: color }));
        self.child = Some(new_cat.clone());
        new_cat
    }
}

fn main() {
    let mut my_cat_maker = CatMaker::new();
    let my_cat = my_cat_maker.make_cat(CoatColor::Black);
    my_cat.borrow_mut().color = CoatColor::Tabby;
}

In make_cat, the .clone() call clones the Rc object, which makes a new reference to the same object, incrementing the reference count. When all related Rc objects are dropped, the reference-counted object is dropped.

In main, we need to call borrow_mut() in order to access the Cat. borrow_mut() returns a RefMut object that protects the Cat from being further borrowed until the RefMut is dropped. If you try to borrow the Cat again while a mutable borrow is active, your program will panic.

Upvotes: 2

Related Questions