为什么我的结构体存活时间不够长?

5
在Rust中,我遇到了以下错误:
<anon>:14:9: 14:17 error: `mystruct` does not live long enough
<anon>:14         mystruct.update();
                  ^~~~~~~~
<anon>:10:5: 17:6 note: reference must be valid for the lifetime 'a as defined on the block at 10:4...
<anon>:10     {
<anon>:11         let initial = vec![Box::new(1), Box::new(2)];
<anon>:12         let mystruct = MyStruct { v : initial, p : &arg };
<anon>:13         
<anon>:14         mystruct.update();
<anon>:15         
          ...
<anon>:12:59: 17:6 note: ...but borrowed value is only valid for the block suffix following statement 1 at 12:58
<anon>:12         let mystruct = MyStruct { v : initial, p : &arg };
<anon>:13         
<anon>:14         mystruct.update();
<anon>:15         
<anon>:16         mystruct
<anon>:17     }
error: aborting due to previous error

for the following code:

struct MyStruct<'a>
{
    v : Vec<Box<i32>>,
    p : &'a i32
}

impl<'a> MyStruct<'a>
{
    fn new(arg : &'a i32) -> MyStruct<'a>
    {
        let initial = vec![Box::new(1), Box::new(2)];
        let mystruct = MyStruct { v : initial, p : &arg };

        mystruct.update();

        mystruct
    }

    fn update(&'a mut self)
    {
        self.p = &self.v.last().unwrap();
    }

}

fn main() {
    let x = 5;
    let mut obj = MyStruct::new(&x);
}

(游乐场)

我不明白为什么mystruct没有足够的生命周期。如果我注释掉mystruct.update()这一行,那么它就能正常工作。更重要的是,如果我注释掉update函数的主体,代码仍然会失败。为什么调用一个借用可变self的空函数会改变事情的发展?

我不明白错误所指的引用是哪一个。有人能解释一下吗?

1个回答

8

这个错误所提到的引用是在调用update()时隐式创建的引用。因为update()采用&'a mut self,意味着它接受一个类型为&'a mut MyStruct<'a>的值。理论上,你应该像这样调用update():

(&mut mystruct).update();

这样在每个地方都写会很不方便,因此Rust可以自动插入必要的&&mut*来调用方法。这被称为"自动引用",它只在方法调用/字段访问中发生。

问题在于update()方法的定义:

impl<'a> MyStruct<'a> {
    ...
    fn update(&'a mut self) { ... }
    ...
}

你在这里请求update()通过一个引用接收调用它的值,该引用的生命周期为'a,其中'a是存储在结构中的引用的生命周期。
然而,当你有一个你正在调用此方法的结构值时,应该已经有一个你在这个结构中存储的i32引用。因此,结构值的生命周期严格小于生命周期参数指定的生命周期,所以使用局部变量(如你的情况)构造&'a mut MyStruct<'a>是不可能的。
解决方案是使用&mut self代替&'a mut self
fn update(&mut self) { ... }
// essentially equivalent to
fn update<'b>(&'b mut self) where 'a: 'b { ... }
// `'b` is a fresh local lifetime parameter

这样,该方法调用中结构的生命周期就不会与该结构包含的引用相关联,并且可以更小。

以下是更详细的解释。

单独看你的定义并不是无意义的。例如:

struct IntRefWrapper<'a> {
    value: &'a i32
}

static X: i32 = 12345;
static Y: IntRefWrapper<'static> = IntRefWrapper { value: &X };

impl<'a> IntRefWrapper<'a> {
    fn update(&'a self) { ... }
}

Y.update();

这里的update()调用不会引起编译错误,因为YX(其引用包含在Y中)的生命周期都是'static

为了进行比较,让我们看看你的例子:

impl<'a> MyStruct<'a> {
    fn new(arg : &'a i32) -> MyStruct<'a> {
        let initial = vec![Box::new(1), Box::new(2)];
        let mystruct = MyStruct { v : initial, p : &arg };

        mystruct.update();

        mystruct
    }
}

这里有一个lifetime参数,'a,由函数的调用者提供。例如,调用者可以使用静态引用来调用此函数:

static X: i32 = 12345;

MyStruct::new(&X);  // here &X has static lifetime

然而,当调用update()方法时,mystruct的生命周期受到其所在块的限制:

{
    let initial = vec![Box::new(1), Box::new(2)];
    let mystruct = MyStruct { v : initial, p : &arg };  // +
                                                        // |
    mystruct.update();                                  // |
                                                        // |
    mystruct                                            // |
}

当然,借用检查器无法证明此生命周期与调用方提供的生命周期相同(对于任何可能的“外部”生命周期,它们确实无法匹配),因此会产生错误。

如果update定义如下:

fn update(&mut self) { ... }
// or, equivalently
fn update<'b>(&'b mut self) where 'a: 'b { ... }

然后当你调用它时,不再需要在调用此方法时,该值必须正好存在与'a'同样长的时间 - 它只需要存在于任何小于或等于'a'的生命周期内,并且函数内部的生命周期完全符合这些要求。因此,您可以在您的值上调用这种方法,编译器不会抱怨。
另外(如评论中所指出的),以下行确实无效,没有办法解决:
self.p = &self.v.last().unwrap();

在这里,借用检查失败,因为您正在尝试将具有延续期的引用存储到结构本身中。一般来说,这是不可能的,因为它会产生令人讨厌的完整性问题。例如,假设您确实能够将此引用存储到结构中。但现在,您无法在结构中更改Vec<Box<i32>>,因为它可能会破坏先前存储的引用所指向的元素,从而使代码内存不安全。
这种情况不可能在静态上进行检查,因此在借用检查级别上是不允许的。事实上,这只是一般借用检查规则的一个好结果。

1
我认为你关于生命周期边界的理解是错误的:语法应该是 'long: 'short (意为 'long 的生命周期比 'short 更长)。因此,它应该写成 fn update<'b>(&'b mut self) where 'a: 'b { ... }。http://is.gd/EEKFnB - mdup
  1. 它没有解决self.p =&self.v.last()。unwrap();这一行无法编译的问题。解释是,恰好在此行处获取的引用不会在update()块之外存在,因此借用检查器正确地拒绝了它。http://is.gd/mlhHvb
- mdup
1
@mdup,update() 中的代码行失败并不是因为引用的生命周期不超过块。它失败是因为该引用将指向结构本身,在 Rust 中这是不可能的。 - Vladimir Matveev
谢谢,这个回答非常有帮助!我确实试图通过使指针指向结构本身来使更新失败。然而,我不记得在 Rust 编程语言书中看到过 'a: 'b 的符号表示法。这在哪里有文档记录?我也在参考资料中找不到它。 - loudandclear
1
@loudandclear,坦白说,我不知道 :) 我相信很久以前我在别人的代码中看到过这个。很可能这种符号表示法尚未被记录下来。 - Vladimir Matveev
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接