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

33,408

Solution 1

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

Solution 2

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.

Share:
33,408

Related videos on Youtube

uwu
Author by

uwu

Updated on July 09, 2022

Comments

  • uwu
    uwu almost 2 years

    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.

    • Matthieu M.
      Matthieu M. over 9 years
      Sibling references (ie, referencing part of the same struct) is not possible in Rust.
  • Sushisource
    Sushisource about 6 years
    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?
  • Paolo Falabella
    Paolo Falabella about 6 years
    @Sushisource technically you could return references with a static lifetime (&'static C), but that's rarely useful in practice
  • nybon
    nybon over 4 years
    Hi Rufflewind, do you know what the alternative is if storing a reference to a portion of struct itself is not possible?
  • zbrojny120
    zbrojny120 over 4 years
    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?
  • GrandOpener
    GrandOpener over 3 years
    @nybon 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).
  • YvesQuemener
    YvesQuemener about 3 years
    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.
  • towry
    towry about 3 years
    Allocate the struct C on heap instead of stack and use RC.