Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Structure containing Vec of Structures and Vec of references to said structures

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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),
        }
    }
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading