Reputation: 3646
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
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