Lifetimes and borrows

I've got a slightly goofy struct setup that gives me errors I don't understand too well. Here it is:

struct A {
    v: Vec<String>,
}

struct B<'a> {
    s: &'a String,
}

struct C<'a> {
    a: A,
    b: B<'a>,
}

impl A {
    pub fn new() -> Self {
        A {
            v: vec!(String::from("")),
        }
    }
}

impl<'a> B<'a> {
    pub fn new(a: &'a A) -> Self {
        B {
            s: &a.v.get(0).unwrap(),
        }
    }
}

impl<'a> C<'a> {
    pub fn new(mut a: A) -> Self {
        C {
            b: B::new(&a),
            a: a,
        }
    }
}

In short, A contains data, B contains a reference to some of that data, and C contains an instance of both A and B.

Here are the errors generated:

error[E0515]: cannot return value referencing function parameter `a`
  --> src/main.rs:38:9
   |
38 | /         C {
39 | |             b: B::new(&a),
   | |                       -- `a` is borrowed here
40 | |             a: a,
41 | |         }
   | |_________^ returns a value referencing data owned by the current function

error[E0505]: cannot move out of `a` because it is borrowed
  --> src/main.rs:40:16
   |
36 |   impl<'a> C<'a> {
   |        -- lifetime `'a` defined here
37 |       pub fn new(mut a: A) -> Self {
38 | /         C {
39 | |             b: B::new(&a),
   | |                       -- borrow of `a` occurs here
40 | |             a: a,
   | |                ^ move out of `a` occurs here
41 | |         }
   | |_________- returning this value requires that `a` is borrowed for `'a`

In the first error, why is a "owned by the current function"? The second error seems to make sense but I'm not sure how to appease it.

Is my general structure here salvageable with edits, or should I try to refactor?

You're trying to build a self-referential struct - this is not feasible in safe Rust, so it'd be probably better to refactor, yes.

For the specific error - well, a is moved into the function (since the argument is just A, not a reference), so what else it should be owned by, unless it's moved somewhere further?

2 Likes

You're trying to build a self-referential struct - this is not feasible in safe Rust, so it'd be probably better to refactor, yes.

I see.

For the specific error - well, a is moved into the function (since the argument is just A , not a reference), so what else it should be owned by, unless it's moved somewhere further?

That makes sense to me, but I think I'm confused as to why it's a problem in this particular instance. If B::new doesn't place a lifetime on a (with other edits to make that work), the same exact C works fine, for example:

struct A {
    v: Vec<String>,
}

struct B<'a> {
    s: &'a str,
}

struct C<'a> {
    a: A,
    b: B<'a>,
}

impl A {
    pub fn new() -> Self {
        A {
            v: vec!(String::from("")),
        }
    }
}

impl<'a> B<'a> {
    pub fn new(a: &A) -> Self {
        B {
            s: "",
        }
    }
}

impl<'a> C<'a> {
    pub fn new(mut a: A) -> Self {
        C {
            b: B::new(&a),
            a: a,
        }
    }
}

I removed the cross-referencing so this compiles.

In this case, B::new borrows its argument only for the duration of the call and releases it immediately after returning, since the returned type (that is, B<'a>) doesn't have any connection with the argument type (that is, &'some_unnamed_very_short_lifetime A). In the original code, however, B::new received &'a A and returned B<'a>, so the input reference is forced to live at least as long as the return value.

1 Like

This helps. Thank you.

The root cause of why you can't build a self-referential struct is this: if the value containing a reference to itself is moved, then the reference would still point to the previous (now invalidated/garbage) value, and not always to the current owner.

("Fixing" this "problem" would require "move constructors" or some other mechanism that would break the trivial movability assumption. Turns out, if you think you need a self-referential struct, you really don't, and trivial movability is much more valuable in practice, especially now that a lot of unsafe code assumes it.)

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.