Search code examples
rustlifetimecontainment

Understanding lifetime management while modeling containment relationship


I'm trying to wrap my head around Rust objects lifetime. As I was performing a relationship modeling exercise I ran into the following error.

error: cannot borrow `bob` as mutable because `bob.gender` is also borrowed as immutable [E0502]

The code is here:

// Business Case:
// Design a person type.  A person may own a Car.  A person should be able to buy and sell cars.
// Two persons should be able to exchange (or trade) their cars.
//
// Purpose of exercise:
// Understand lifetime management in Rust while modeling containment relationship.
// (meaning: when an object contains a reference to another object.)

struct Car {
    make: &'static str,
    model: &'static str,
    year: &'static str,
}

struct Person<'a> {
    name: &'static str,
    gender: &'static str,
    car: Option<&'a Car>,
}

impl<'a> Person<'a> {
    fn new(name: &'static str, gender: &'static str, car: Option<&'a Car>) -> Person<'a> {
        Person {
            name: name,
            gender: gender,
            car: None,
        }
    }

    fn buy_car(&mut self, c: &'a Car) {
        self.car = Some(c);
    }

    fn sell_car(&mut self) {
        self.car = None;
    }
}

fn main() {
    let pickup = Car {
        make: "Ford",
        model: "F250",
        year: "2006",
    };

    let mut bob = Person::new("Bob", "Male", None);

    println!("A {:?} whose name is {:?} has just purchased a 2006 {:?}.",
             bob.gender,
             bob.name,
             bob.buy_car(&pickup));
}

Can anyone chime in on what exactly am I missing here? I'm not exactly sure if reference count or Box is the way to go, need a bit more insight.


Solution

  • Your issue boils down to you using buy_car (which returns nothing) where you probably meant to use bob.car. To do that.. you'll want to have fmt::Debug implemented for your Car struct as well. Here is a working fix for you.. note all of the // <----- parts I added (here it is on the Playground):

    #[derive(Debug)] // <------------ Have the compiler implement fmt::Debug for you
    struct Car {
        make: &'static str,
        model: &'static str,
        year: &'static str,
    }
    
    struct Person<'a> {
        name: &'static str,
        gender: &'static str,
        car: Option<&'a Car>,
    }
    
    impl<'a> Person<'a> {
        fn new(name: &'static str, gender: &'static str, car: Option<&'a Car>) -> Person<'a> {
            Person {
                name: name,
                gender: gender,
                car: None,
            }
        }
    
        fn buy_car(&mut self, c: &'a Car) {
            self.car = Some(c);
        }
    
        fn sell_car(&mut self) {
            self.car = None;
        }
    }
    
    fn main() {
        let pickup = Car {
            make: "Ford",
            model: "F250",
            year: "2006",
        };
    
        let mut bob = Person::new("Bob", "Male", None);
    
        bob.buy_car(&pickup);   // <------- Buy the car separately
    
        println!("A {:?} whose name is {:?} has just purchased a 2006 {:?}.",
                 bob.gender,
                 bob.name,
                 bob.car); // <------------ Pass the car into the debug string
    }
    

    As an aside - I would look in to using String where appropriate to reduce the need to pass references and lifetimes around. Maybe not so important in your small example but as the code gets bigger they can become tricky.