HiveBrain v1.2.0
Get Started
← Back to all entries
patternrustMajor

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

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
withstructusecorrectthelifetimeswayrustwhat

Problem

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 {
    c: &'b C,
}

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

impl A {
    fn new() -> A {
        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() -> A {
| ^^^^^^^^^^^^^^^^^^^^^

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.

Solution

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 {
    c: &'b C,
}

struct A {
    b: B,
    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 {
    c: &'b C,
}

struct A {
    b: B,
    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 {
    fn new() -> 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 {
| ^^^^^^^^^^^^^^

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 {
| ^^^^^^^^^^^^^^


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 {
    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 {
    c: &'b C,
}

struct A {
    b: B,
    c: &'a C
}

fn main() {
    let c1 = C;
    let _ = A::new(&c1);
}

impl A {
    fn new(c: &'a C) -> A {
        A {c: c, b: B{c: c}}
    }
}


playground

Code Snippets

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 },
    };
}
#[derive(Copy, Clone)]
struct C;
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 },
    };
}
impl<'a> A<'a> {
    fn new() -> A<'a> {
        let c1 = C;
        A {
            c: &c1,
            b: B { c: &c1 },
        }
    }
}
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)

Context

Stack Overflow Q#27589054, score: 96

Revisions (0)

No revisions yet.