在Rust中使用struct的生命周期的正确方法是什么?

Qua*_*rew 25 lifetime rust

我想写这个结构:

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

struct B {
    c: &C,
}

struct C;
Run Code Online (Sandbox Code Playgroud)

B.c应借A.c.

A ->
  b: B ->
    c: &C -- borrow from --+
                           |
  c: C  <------------------+
Run Code Online (Sandbox Code Playgroud)

这是我尝试过的: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() {}
Run Code Online (Sandbox Code Playgroud)

但它失败了:

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
Run Code Online (Sandbox Code Playgroud)

我已阅读有关所有权的Rust文档,但我仍然不知道如何修复它.

Pao*_*lla 37

实际上,上述代码失败的原因不止一个.让我们分解一下,探索一些如何解决它的选项.

首先让我们删除new并尝试A直接构建一个实例main,以便您看到问题的第一部分与生命周期无关:

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 },
    };
}
Run Code Online (Sandbox Code Playgroud)

这失败了:

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
Run Code Online (Sandbox Code Playgroud)

它说的是,如果分配c1c,您将其所有权c(即你不能再通过访问它c1,只能通过c).这意味着所有引用c1将不再有效.但是你&c1仍然在范围内(在B中),所以编译器不能让你编译这段代码.

当编译器声明该类型C是不可复制的时,编译器会在错误消息中提示可能的解决方案.如果你可以做的副本C,你的代码将被有效,因为分配c1c会创造价值,而不是移动的原件所有权的新副本.

我们可以C通过改变它的定义来复制:

#[derive(Copy, Clone)]
struct C;
Run Code Online (Sandbox Code Playgroud)

现在上面的代码工作了.请注意,@ matthieu-m注释仍然是正确的:我们不能同时存储对值的引用和值本身(我们在这里存储对值的引用和值的COPY).不仅仅是结构,这是所有权的运作方式.

现在,如果您不想(或不能)制作可C复制的,您可以在两者中存储引用,AB不是.

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 },
    };
}
Run Code Online (Sandbox Code Playgroud)

一切都好吗?不是......我们仍然希望将创建A重新转移到new方法中.而这就是我们将终生遇到麻烦的地方.让我们将创建A重新转移到一个方法中:

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

正如预期的那样,这是我们的终身错误:

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> {
   | ^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

这是因为c1new方法结束时被销毁,所以我们无法返回对它的引用.

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)
Run Code Online (Sandbox Code Playgroud)

一种可能的解决方案是在C外部创建new并将其作为参数传递:

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}}
    }
}
Run Code Online (Sandbox Code Playgroud)

操场

  • 在类似的情况下,我最终使用了“Arc&lt;&gt;”,在我看来,这有点矫枉过正,但我​​还没有看到更甲壳类的做法。 (2认同)

Ruf*_*ind 6

在#rust IRC 上检查 Manishearth 和 eddyb 后,我相信结构不可能存储对自身或自身一部分的引用。所以你想要做的在 Rust 的类型系统中是不可能的。