1

The blog post series Understanding Lifetime in Rust ends with a cliffhanger summarized as follows...

struct Car {
    model: String,
}

struct Person<'a> {
    car: Option<&'a Car>,
}

impl<'a> Person<'a> {
    fn new() -> Person<'a> {
        Person { car: None }
    }

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

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

    fn trade_with(&mut self, other: &mut Person<'a>) {
        let tmp = other.car;

        other.car = self.car;
        self.car = tmp;
    }
}

fn shop_for_car(p: &mut Person) {
    let car = Car {
        model: "Mercedes GLK350".to_string(),
    };

    p.buy_car(&car); //Error! car doesn't live long enough
}

fn main() {
    let mut p = Person::new();
    shop_for_car(&mut p);
}

Sure enough, when compiled, car does not live long enough.

error[E0597]: `car` does not live long enough
  --> src/main.rs:35:16
   |
35 |     p.buy_car(&car); //Error! car doesn't live long enough
   |                ^^^ does not live long enough
36 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #2 defined on the function body at 30:1...
  --> src/main.rs:30:1
   |
30 | / fn shop_for_car(p: &mut Person) {
31 | |     let car = Car {
32 | |         model: "Mercedes GLK350".to_string(),
33 | |     };
34 | |
35 | |     p.buy_car(&car); //Error! car doesn't live long enough
36 | | }
   | |_^

The post ends claiming to solve this in Part III...

That is because the car object simply doesn’t live as long as the Person buying it. So how can we keep reference to an object that is created in an inner scope like a function? The answer lies in heap allocation which in Rust is achieved via Box::new. We will explore that in Part III.

...but there is no Part III.

I'm trying to solve a very similar problem and having Part III would help.

Can you answer with Part III?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Schwern
  • 153,029
  • 25
  • 195
  • 336

1 Answers1

1

The answer lies in heap allocation which in Rust is achieved via Box::new.

Heap allocation via Box is not needed; just take ownership of the Car:

struct Car {
    model: String,
}

struct Person {
    car: Option<Car>,
}

impl Person {
    fn new() -> Person {
        Person { car: None }
    }

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

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

    fn trade_with(&mut self, other: &mut Person) {
        std::mem::swap(&mut self.car, &mut other.car);
    }
}

fn shop_for_car(p: &mut Person) {
    let car = Car {
        model: "Mercedes GLK350".to_string(),
    };

    p.buy_car(car);
}

fn main() {
    let mut p = Person::new();
    shop_for_car(&mut p);
}


fn main() {
    let mut p = Person::new();
    shop_for_car(&mut p);
}

If you want to spuriously allocate memory on the heap, you are welcome to do so by changing to Box<Car>.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thank you for your answer. However in the post they reject having Person own Car. "*This is easy from a memory management point of view. But it has several problems. A car is not an integral part of a person. Buying and trading in cars will create copies of cars. For performance reasons we will like to avoid that. Instead, we will like Person to store an optional reference to a Car. But that’s when things begin get complicated.*" I left out some code, like trading. I'll add that to the question. – Schwern Dec 15 '17 at 03:57
  • @Schwern *A car is not an integral part of a person* — yes, when you model things in software sometimes they differ from "real life", but that's nothing new. *Buying and trading in cars will create copies of cars* — there's not enough to go on here. Which copies are being made? There's no calls to `Clone`. Some bits *might* be moved between stack frames sure, but the optimizer usually deals with those handily. If that truly becomes your bottleneck (unlikely, profile to determine), then sure you can move to a `Box` if you need to. – Shepmaster Dec 15 '17 at 04:15
  • This particular question is an example of a larger pattern of problems I've run into with references which don't appear to be solvable by ownership. So I'd be interested to see how it's solved without ownership. I didn't know about [std::boxed](https://doc.rust-lang.org/std/boxed/), so you've already helped. Maybe I should finish reading TRPL first... – Schwern Dec 15 '17 at 04:27
  • @Schwern yes, **please** do read the book. A *lot* of effort has been spent to address all sorts of introductory questions. The code you've presented has no problems that cannot be solved with ownership. With over 70k reputation, you already know that if you have a question about a piece of course, you have to show us *that code*, not something unrelated. – Shepmaster Dec 15 '17 at 04:37
  • [This earlier question is about what I'm working on](https://stackoverflow.com/questions/47784458/how-do-i-store-a-struct-in-two-places) and the real code [can be found here](https://github.com/schwern/adventofcode.rust/tree/07f8bed864ff9520ebcafcd3489475b694a2ba6f/2017/day07/src). I fixed the earlier problem, but now I'm running into the lifetime problem similar to what I asked about here. Maybe this would be better for CodeReview.SE? – Schwern Dec 15 '17 at 05:01