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

4

63

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.

Silvey answered 21/12, 2014 at 11:47 Comment(1)
Sibling references (ie, referencing part of the same struct) is not possible in Rust.Platypus
H
92

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

Homer answered 21/12, 2014 at 14:57 Comment(5)
Do you (or anyone else) know if there's a way to make the compiler happy while still creating "C" inside of the new fn?Nikitanikki
@Nikitanikki technically you could return references with a static lifetime (&'static C), but that's rarely useful in practiceHomer
Does anyone know if now it would somehow be possible to do something about this using Pins? The example in the docs shows how to achieve something similar by replacing &'a T with *const T. However, I don't see any way to somehow replace B<'a> with a raw pointer. Is there any?Riti
In a similar situation I ended up using Arc<> for this case, which is a bit overkill IMO, but I did not see a more crustacean way of doing yet.Skinner
Allocate the struct C on heap instead of stack and use RC.Sidhu
I
8

After checking with Manishearth and eddyb on the #rust IRC, I believe it's not possible for a struct to store a reference to itself or a portion of itself. So what you are trying to do isn't possible within Rust's type system.

Itchy answered 21/12, 2014 at 12:57 Comment(3)
Hi Rufflewind, do you know what the alternative is if storing a reference to a portion of struct itself is not possible?Cockeye
@Cockeye The most typical solution would be one of either referring to the value directly (i.e. self.b.c in the above example, with self.c omitted entirely), or if that is undesirable, providing a method that generates references to C on demand (and those references can correctly be annotated with the struct's lifetime).Perpetuity
This is a problem. I'm using libgit2 (git2) and everything points to the repository. So if I want to create my own wrapper that stores some things like heads, branches, the last commit for the graph... everything needs to be tied to the repository.Ecbatana
E
3

Late to the party (replying from the future) and totally new to Rust, but I am getting there (sort of). Building on the answer this worked for me, at least compile wise.

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

}

Exuviate answered 27/6, 2022 at 3:5 Comment(1)
This... is not going to work 95% of the cases. It only works because C is special.Nedry
C
1

Check out the ouroboros crate:

use ouroboros::self_referencing;

struct C;

struct B<'b> {
    c: &'b C,
}

#[self_referencing]
struct A {
    c: C,

    #[borrows(c)]
    #[covariant]
    b: B<'this>,
}

fn main() {
    let c = C;

    let a = A::new(c, |c| B { c: &c });
}
Cumulous answered 11/9, 2023 at 22:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.