在 Rust 中使用带有结构的生命周期的正确方法是啥?

Posted

技术标签:

【中文标题】在 Rust 中使用带有结构的生命周期的正确方法是啥?【英文标题】:What is the correct way to use lifetimes with a struct in Rust?在 Rust 中使用带有结构的生命周期的正确方法是什么? 【发布时间】:2015-02-19 17:56:23 【问题描述】:

我想写这个结构:

struct A 
    b: B,
    c: C,


struct B 
    c: &C,


struct C;

B.c应该是从A.c借来的。

A ->
  b: B ->
    c: &C -- borrow from --+
                           |
  c: C  <------------------+

这是我尝试过的: 结构 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() 

但它失败了:

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

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

【问题讨论】:

兄弟引用(即,引用同一结构的一部分)在 Rust 中是不可能的。 【参考方案1】:

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

【讨论】:

嗨,Rufflewind,如果无法存储对 struct 本身的一部分的引用,您知道替代方案是什么吗? @nybon 最典型的解决方案是直接引用该值(即上面示例中的self.b.c,完全省略self.c),或者如果不希望这样做,则提供一个按需生成对 C 的引用的方法(并且这些引用可以正确地用结构的生命周期进行注释)。【参考方案2】:

实际上上面的代码失败的原因不止一个。让我们稍微分解一下,并探讨一些解决方法。

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

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 ,
    ;

这失败了:

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

它的意思是,如果您将c1 分配给c,您将其所有权转移到c(即您不能再通过c1 访问它,只能通过c)。这意味着对c1 的所有引用都将不再有效。但是你有一个&amp;c1 仍然在范围内(在 B 中),所以编译器不能让你编译这段代码。

当编译器指出类型C 不可复制时,它会在错误消息中提示可能的解决方案。如果您可以复制C,那么您的代码将是有效的,因为将c1 分配给c 将创建该值的新副本,而不是移动原始副本的所有权。

我们可以通过如下改变C 的定义使C 可复制:

#[derive(Copy, Clone)]
struct C;

现在上面的代码可以工作了。请注意,@matthieu-m comments 仍然是正确的:we can't store both the reference to a value and the value itself in B(我们在此处存储对值的引用和该值的副本)。不过,这不仅适用于结构,还有所有权的运作方式。

现在,如果您不想(或不能)使 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 ,
    ;

那么一切都好吗?不是真的......我们仍然想将A 的创建移回new 方法。这就是我们一生都会遇到麻烦的地方。让我们将 A 的创建移回一个方法中:

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

正如预期的那样,这是我们一生的错误:

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

这是因为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)

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

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: Bc: c
    

playground

【讨论】:

您(或其他任何人)是否知道是否有办法让编译器满意,同时仍在新 fn 中创建“C”? @Sushisource 从技术上讲,您可以返回具有静态生命周期的引用 (&amp;'static C),但这在实践中很少有用 有谁知道现在是否可以使用Pins 来解决这个问题?文档中的示例显示了如何通过将&amp;'a T 替换为*const T 来实现类似的效果。但是,我看不到任何方法可以用原始指针替换B&lt;'a&gt;。有吗? 在类似的情况下,我最终在这种情况下使用了Arc&lt;&gt;,这在 IMO 中有点矫枉过正,但我​​还没有看到更甲壳类动物的做法。 在堆而不是堆栈上分配结构 C 并使用 RC。

以上是关于在 Rust 中使用带有结构的生命周期的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

了解Rust中参数化结构的生命周期

我啥时候需要在 Rust 中指定显式生命周期?

为啥 Rust 编译器要求我限制泛型类型参数的生命周期(错误 E0309)?

正确使用 asp.net webforms 生命周期

使用字符串时 Rust 中的生命周期

Rust学习笔记1.基础语法