为什么我的结构不够长寿?

lou*_*ear 5 lifetime rust

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

对于以下代码:

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

(操场)

我不明白为什么mystruct活不够.如果我注释掉它的mystruct.update()行,它可以正常工作.更重要的是,如果我注释掉update代码的主体仍然失败.为什么调用一个借用可变变量的空函数会self改变一些东西?

我不明白哪个参考是错误所涉及的那个.有人可以解释一下吗?

Vla*_*eev 8

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

(&mut mystruct).update();
Run Code Online (Sandbox Code Playgroud)

在任何地方编写它都会非常不方便,因此Rust能够自动插入必要的&s,&muts和*s以调用方法.这称为"自动引用",它发生的唯一地方是方法调用/字段访问.

问题是update()方法的定义:

impl<'a> MyStruct<'a> {
    ...
    fn update(&'a mut self) { ... }
    ...
}
Run Code Online (Sandbox Code Playgroud)

在这里,您要求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
Run Code Online (Sandbox Code Playgroud)

这样,此方法调用中结构的生命周期与此结构包含的引用无关,并且可以更小.

下面将进行更深入的解释.

你自己的定义本身并不是无稽之谈.例如:

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

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

这里我们有一个生命周期参数,'a由函数的调用者提供.例如,调用者可以使用静态引用调用此函数:

static X: i32 = 12345;

MyStruct::new(&X);  // here &X has static lifetime
Run Code Online (Sandbox Code Playgroud)

但是,当update()调用方法时,mystruct生命周期受到调用它的块的限制:

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

当然,借用检查器无法证明此生命周期与调用者提供的生命周期相同(并且对于任何可能的"外部"生命周期,它们确实不可能匹配),因此它会引发错误.

更新定义如下:

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

然后当你调用它时,不再要求你调用这个方法的值必须完全和'a它一样长- 它足以让它适用于任何小于或等于'a的生命周期 - 以及函数内部的生命周期完全符合这些要求.因此,您可以在您的值上调用此类方法,编译器不会抱怨.

另外(正如评论中所注意到的)以下行确实无效,并且没有办法解决它:

self.p = &self.v.last().unwrap();
Run Code Online (Sandbox Code Playgroud)

借用检查在这里失败,因为您试图将具有结构生命周期的引用存储到结构本身中.一般来说,这是不可能的,因为它有令人讨厌的健全性问题.例如,假设您确实能够将此引用存储到结构中.但是现在你不能Vec<Box<i32>>在结构中变异,因为它可能会破坏先前存储的引用所指向的元素,从而使代码内存不安全.

静态检查这些东西是不可能的,因此在借用检查级别上是不允许的.事实上,这只是一般借款检查规则的一个很好的结果.