Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the correct way to use lifetimes with a struct in Rust?

Tags:

rust

lifetime

I want to write this structure:

struct A {     b: B,     c: C, }  struct B {     c: &C, }  struct C; 

The B.c should be borrowed from A.c.

A ->   b: B ->     c: &C -- borrow from --+                            |   c: C  <------------------+ 

This is what I tried: struct C;

struct B<'b> {     c: &'b C, }  struct A<'a> {     b: B<'a>,     c: C, }  impl<'a> A<'a> {     fn new<'b>() -> A<'b> {         let c = C;         A {             c: c,             b: B { c: &c },         }     } }  fn main() {} 

But it fails:

error[E0597]: `c` does not live long enough   --> src/main.rs:17:24    | 17 |             b: B { c: &c },    |                        ^ borrowed value does not live long enough 18 |         } 19 |     }    |     - borrowed value only lives until here    | note: borrowed value must be valid for the lifetime 'b as defined on the method body at 13:5...   --> src/main.rs:13:5    | 13 |     fn new<'b>() -> A<'b> {    |     ^^^^^^^^^^^^^^^^^^^^^  error[E0382]: use of moved value: `c`   --> src/main.rs:17:24    | 16 |             c: c,    |                - value moved here 17 |             b: B { c: &c },    |                        ^ value used here after move    |    = note: move occurs because `c` has type `C`, which does not implement the `Copy` trait 

I've read the Rust documentation on ownership, but I still don't know how to fix it.

like image 612
uwu Avatar asked Dec 21 '14 11:12

uwu


People also ask

How do lifetimes work in Rust?

Lifetimes are what the Rust compiler uses to keep track of how long references are valid for. Checking references is one of the borrow checker's main responsibilities. Lifetimes help the borrow checker ensure that you never have invalid references.

Why are lifetimes needed in Rust?

Rather than ensuring that a type has the behavior we want, lifetimes ensure that references are valid as long as we need them to be. One detail we didn't discuss in the “References and Borrowing” section in Chapter 4 is that every reference in Rust has a lifetime, which is the scope for which that reference is valid.

What are lifetime parameters Rust?

Rust uses lifetime parameters to avoid such potential run-time errors. Since the compiler doesn't know in advance whether the if or the else block will execute, the code above won't compile and an error message will be printed that says, “a lifetime parameter is expected in compare 's signature.”

What is static lifetime Rust?

A static is never "inlined" at the usage site, and all references to it refer to the same memory location. Static items have the static lifetime, which outlives all other lifetimes in a Rust program. Static items may be placed in read-only memory if the type is not interior mutable.


1 Answers

There is actually more than one reason why the code above fails. Let's break it down a little and explore a few options on how to fix it.

First let's remove the new and try building an instance of A directly in main, so that you see that the first part of the problem has nothing to do with lifetimes:

struct C;  struct B<'b> {     c: &'b C, }  struct A<'a> {     b: B<'a>,     c: C, }  fn main() {     // I copied your new directly here     // and renamed c1 so we know what "c"     // the errors refer to     let c1 = C;      let _ = A {         c: c1,         b: B { c: &c1 },     }; } 

this fails with:

error[E0382]: use of moved value: `c1`   --> src/main.rs:20:20    | 19 |         c: c1,    |            -- value moved here 20 |         b: B { c: &c1 },    |                    ^^ value used here after move    |    = note: move occurs because `c1` has type `C`, which does not implement the `Copy` trait 

what it says is that if you assign c1 to c, you move its ownership to c (i.e. you can't access it any longer through c1, only through c). This means that all the references to c1 would be no longer valid. But you have a &c1 still in scope (in B), so the compiler can't let you compile this code.

The compiler hints at a possible solution in the error message when it says that type C is non-copyable. If you could make a copy of a C, your code would then be valid, because assigning c1 to c would create a new copy of the value instead of moving ownership of the original copy.

We can make C copyable by changing its definition like this:

#[derive(Copy, Clone)] struct C; 

Now the code above works. Note that what @matthieu-m comments is still true: we can't store both the reference to a value and the value itself in B (we're storing a reference to a value and a COPY of the value here). That's not just for structs, though, it's how ownership works.

Now, if you don't want to (or can't) make C copyable, you can store references in both A and B instead.

struct C;  struct B<'b> {     c: &'b C, }  struct A<'a> {     b: B<'a>,     c: &'a C, // now this is a reference too }  fn main() {     let c1 = C;     let _ = A {         c: &c1,         b: B { c: &c1 },     }; } 

All good then? Not really... we still want to move the creation of A back into a new method. And THAT's where we will run in trouble with lifetimes. Let's move the creation of A back into a method:

impl<'a> A<'a> {     fn new() -> A<'a> {         let c1 = C;         A {             c: &c1,             b: B { c: &c1 },         }     } } 

as anticipated, here's our lifetime error:

error[E0597]: `c1` does not live long enough   --> src/main.rs:17:17    | 17 |             c: &c1,    |                 ^^ borrowed value does not live long enough ... 20 |     }    |     - borrowed value only lives until here    | note: borrowed value must be valid for the lifetime 'a as defined on the impl at 13:1...   --> src/main.rs:13:1    | 13 | impl<'a> A<'a> {    | ^^^^^^^^^^^^^^  error[E0597]: `c1` does not live long enough   --> src/main.rs:18:24    | 18 |             b: B { c: &c1 },    |                        ^^ borrowed value does not live long enough 19 |         } 20 |     }    |     - borrowed value only lives until here    | note: borrowed value must be valid for the lifetime 'a as defined on the impl at 13:1...   --> src/main.rs:13:1    | 13 | impl<'a> A<'a> {    | ^^^^^^^^^^^^^^ 

this is because c1 is destroyed at the end of the new method, so we can't return a reference to it.

fn new() -> A<'a> {     let c1 = C; // we create c1 here     A {         c: &c1,          // ...take a reference to it         b: B { c: &c1 }, // ...and another     } } // and destroy c1 here (so we can't return A with a reference to c1) 

One possible solution is to create C outside of new and pass it in as a parameter:

struct C;  struct B<'b> {     c: &'b C, }  struct A<'a> {     b: B<'a>,     c: &'a C }  fn main() {     let c1 = C;     let _ = A::new(&c1); }  impl<'a> A<'a> {     fn new(c: &'a C) -> A<'a> {         A {c: c, b: B{c: c}}     } } 

playground

like image 127
Paolo Falabella Avatar answered Oct 14 '22 15:10

Paolo Falabella