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.
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.
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.
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.”
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With