I’m trying to create a Rust program that has a vector of people, and another vector of events (such as birth and death) that reference said people.
However, I can’t figure out a way to both 1) move the ownership of the person to one vector, and 2) keep the reference in another vector.
Below I have pasted a heavily simplified version of the application with my current issue and some attempts to solve it.
struct Person {
name: String
}
enum Event<'a> {
PersonBorn(i32, &'a Person),
PersonDied(i32, &'a Person),
}
struct Universe<'a> {
people: Vec<Person>,
events: Vec<Event<'a>>
}
fn main() {
let mut data = Universe {
people: Vec::new(),
events: Vec::new()
};
let names = ["John", "Joe"];
for name in names {
let year = 1993;
let person = Person {
name: String::from(name)
};
data.people.push(person);
// Attempt 1: Doesn't work because "person" was moved on "push' on line 29
data.events.push(Event::PersonBorn(year, &person));
// Attempt 2: Doesn't work because it's an immutable borrow of "data",
// while doing a mutable borrow on line 29
// data.events.push(Event::PersonBorn(year, &data.people.last().unwrap()));
}
// Attempt 3: In this simplified example, this works, but in my real
// program I would not know the year anymore at this point
//for person in data.people.iter() {
// data.events.push(Event::PersonBorn(year, person));
//}
for event in data.events {
match event {
Event::PersonBorn(year, person) => println!("{} was born in {}", person.name, year),
Event::PersonDied(year, person) => println!("{} died in {}", person.name, year),
}
}
}
Now, I understand why attempt 1 (borrow after move) and attempt 2 (mutable and immutable references in for-loop) fail.
However, the only workaround I can think of is attempt 3, which is not viable in the real application.
Is there any other way of doing this that I can’t see?
>Solution :
Arguably, the problem dates back to this struct definition:
struct Universe<'a> {
people: Vec<Person>,
events: Vec<Event<'a>>
}
This would require the contents of the owned events field to point to the contents of the owned people field. If this did work somehow, calling universe.people.clear() would create dangling references. As such, this struct is self-referential and impossible.
One easy way to preserve your intentions is to use reference-counting in the form of Rc:
use std::rc::Rc;
struct Person {
name: String
}
enum Event {
PersonBorn(i32, Rc<Person>),
PersonDied(i32, Rc<Person>),
}
struct Universe {
people: Vec<Rc<Person>>,
events: Vec<Event>
}
fn main() {
let mut data = Universe {
people: Vec::new(),
events: Vec::new()
};
let names = ["John", "Joe"];
for name in names {
let year = 1993;
let person = Rc::new(Person {
name: String::from(name)
});
data.people.push(Rc::clone(&person));
data.events.push(Event::PersonBorn(year, Rc::clone(&person)));
}
for event in data.events {
match event {
Event::PersonBorn(year, person) => println!("{} was born in {}", person.name, year),
Event::PersonDied(year, person) => println!("{} died in {}", person.name, year),
}
}
}
Now, regardless of what happens with the people field, the Event‘s have a valid Rc-reference to a Person. That’s because Rc‘s keep their contents alive until the last Rc-reference is dropped.
An alternative is to make a PersonId scalar type and store Person‘s in a HashMap:
use std::collections::HashMap;
struct Person {
name: String
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
struct PersonId(u64);
enum Event {
PersonBorn(i32, PersonId),
PersonDied(i32, PersonId),
}
struct Universe {
people: HashMap<PersonId, Person>,
events: Vec<Event>
}
fn main() {
let mut data = Universe {
people: HashMap::new(),
events: Vec::new()
};
let names = ["John", "Joe"];
for name in names {
let year = 1993;
use rand::Rng;
let person_id = PersonId(rand::thread_rng().gen());
let person = Person {
name: String::from(name)
};
data.people.insert(person_id, person);
data.events.push(Event::PersonBorn(year, person_id));
}
for event in data.events {
match event {
Event::PersonBorn(year, person) => println!("{} was born in {}", data.people.get(&person).unwrap().name, year),
Event::PersonDied(year, person) => println!("{} died in {}", data.people.get(&person).unwrap().name, year),
}
}
}