Reputation: 23647
I have a collection of objects owned by a struct Manager
. These objects have optional attributes such as printable
or dynamic
. I want to repeatedly loop over all printable objects to print them and loop over all dynamic objects to update them. My rather naive implementation is this:
struct Object {
state: i32,
printable: Option<i32>,
dynamic: Option<i32>
}
struct Manager {
objects: Vec<Object>,
}
impl Manager {
fn print_objects(&self) {
for o in &self.objects {
if let Some(i) = o.printable {
print!("{}: {}, ", i, o.state);
}
}
println!();
}
fn update_objects(&mut self) {
for o in &mut self.objects {
if let Some(i) = o.dynamic {
o.state += i;
}
}
}
}
fn main() {
let mut mgr = Manager{objects: Vec::new()};
mgr.objects.push(Object{state: 0, printable: Some(10), dynamic: None});
mgr.objects.push(Object{state: 0, printable: None, dynamic: Some(1)});
mgr.objects.push(Object{state: 0, printable: Some(20), dynamic: Some(2)});
for _ in 0..3 {
mgr.update_objects();
mgr.print_objects();
}
}
This solution has the drawback that it needs to loop over all objects and check each for the appropriate flag. Assuming that only a small fraction of the objects are dynamic, I'd rather avoid looping over all of them. In C++ I would simply create a list of pointers to the dynamic objects and loop over that. Attempting this:
struct Manager<'a> {
objects: Vec<Object>, // objects owned by Manager
dynamic: Vec<&'a Object> // references to dynamic objects
}
impl<'a> Manager<'a> { /* ... */ }
fn main() {
let mut mgr = Manager{objects: Vec::new(), dynamic: Vec::new()};
mgr.objects.push(Object{state: 0, printable: Some(10), dynamic: None});
mgr.objects.push(Object{state: 0, printable: None, dynamic: Some(1)});
mgr.objects.push(Object{state: 0, printable: Some(20), dynamic: Some(2)});
mgr.dynamic.push(&mgr.objects[1]);
mgr.dynamic.push(&mgr.objects[2]);
for _ in 0..3 {
mgr.update_objects(); // can't mutably borrow because of reference held by mgr.dynamic
mgr.print_objects();
}
}
It seems to be a very basic problem that I can't keep a reference to elements in Manager.objects
and at the same time borrow it mutably. Thus, I've started to doubt my whole approach. Is there an idiomatic way to implement such a pattern in Rust?
I believe that keeping references to objects prevents me from mutating them, but I'd be happy to learn a way around that. A future goal would be to change printable
or dynamic
during update, but I'd like to figure out the basic stuff before tackling that step.
Upvotes: 1
Views: 115
Reputation: 431669
struct Manager<'a> {
objects: Vec<Object>, // objects owned by Manager
dynamic: Vec<&'a Object> // references to dynamic objects
}
This does not work and has been thoroughly discussed in Why can't I store a value and a reference to that value in the same struct?.
One solution is to have multiple owners of a single value, known as shared ownership. One implementation is Rc
.
You also want to have mutability that is not tied to the scope of the program but instead to the runtime characteristics, which can be enabled with interior mutability. One implementation of is RefCell
:
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct Object {
state: i32,
printable: Option<i32>,
dynamic: Option<i32>,
}
#[derive(Debug, Default)]
struct Manager {
objects: Vec<Rc<RefCell<Object>>>,
printable: Vec<Rc<RefCell<Object>>>,
dynamic: Vec<Rc<RefCell<Object>>>,
}
impl Manager {
fn add(&mut self, state: i32, printable: Option<i32>, dynamic: Option<i32>) {
let obj = Object { state, printable, dynamic };
let obj = Rc::new(RefCell::new(obj));
self.objects.push(obj.clone());
if printable.is_some() {
self.printable.push(obj.clone())
}
if dynamic.is_some() {
self.dynamic.push(obj.clone())
}
}
fn print_objects(&self) {
for o in &self.printable {
let o = o.borrow();
if let Some(i) = o.printable {
print!("{}: {}, ", i, o.state);
}
}
println!();
}
fn update_objects(&mut self) {
for o in &self.dynamic {
let mut o = o.borrow_mut();
if let Some(i) = o.dynamic {
o.state += i;
}
}
}
}
fn main() {
let mut mgr = Manager::default();
mgr.add(0, Some(10), None);
mgr.add(0, None, Some(1));
mgr.add(0, Some(20), Some(2));
for _ in 0..3 {
mgr.update_objects();
mgr.print_objects();
}
}
You still have all the inherent problems of transitioning an item from one group to another.
Upvotes: 2