我如何使用生命周期来解决 "reference must be valid for the static lifetime"

How can I use lifetime bounds to solve "reference must be valid for the static lifetime"

我是一个 Rust 初学者,我无法编译以下代码。 我想要的是将几个特征存储在一个向量中,每个特征还应该具有对借用变量的只读访问权限。

我猜我必须使用“Lifetime bounds”——就像本文 中讨论的那样——因为如果我注释掉第 60-68 行,代码编译正常。

有人可以解释一下如何使用“lifetime bounds”——如果这是解决问题的方法——或者这不是 Rust 解决问题的方法吗?如果有更好的方法来实现我正在尝试做的事情,我很乐意改变我解决问题的方法。

无法编译的代码在此处和 rust-playground

struct PixelImageSimple<'a> {
    pixels: &'a Vec<i32>,
    width: i32,
    height: i32,
}

trait ImageOperation<'a> {
    fn execute_op(&self);
}

struct ImageOperationSharpen<'a> {
    val: i32,
    bitmapdata: &'a PixelImageSimple<'a>
}

impl<'a> ImageOperation<'a> for ImageOperationSharpen<'a> {
    fn execute_op(&self) {
        println!("ImageOperationSharpen - val = {}, width = {}, height = {}, pixels = {:?}",
            &self.val, &self.bitmapdata.width, &self.bitmapdata.height,&self.bitmapdata.pixels);
    }
}

struct ImageOperationRotate<'a> {
    angle: f64,
    bitmapdata: &'a PixelImageSimple<'a>
}

impl<'a> ImageOperation<'a> for ImageOperationRotate<'a> {
    fn execute_op(&self) {
        println!("ImageOperationRotate - angle = {}, width = {}, height = {}, pixels = {:?}",
            &self.angle, &self.bitmapdata.width, &self.bitmapdata.height,&self.bitmapdata.pixels);
    }
}

struct Image<'a> {
    image_operations: Vec<Box<ImageOperation<'a>>>
}

impl<'a> Image<'a> {
    fn new() -> Image<'a> {
        Image { image_operations: vec![] }
    }

    fn add_op(&mut self, image_ops: Box<ImageOperation<'a>>) {
           self.image_operations.push(image_ops);
    }
}

fn main () {
    let bitmapdata = vec![1,2,3];

    let bitmap = PixelImageSimple { pixels: &bitmapdata, width: 222, height:334 };

    let sharpen = ImageOperationSharpen { val: 34, bitmapdata: &bitmap };
    let rotate = ImageOperationRotate { angle: 13.32, bitmapdata: &bitmap };

    let box_sharpen = Box::new(sharpen);
    let box_rotate = Box::new(rotate);

    let mut image = Image::new();

    image.add_op(box_sharpen);
    image.add_op(box_rotate);

    println!("execute_op()");
    for imageops in image.image_operations.iter() {
        imageops.execute_op();
    }
}

变量 'bitmapdata' 和 'bitmap' 两次出现 3 个错误。 正如我上面提到的:代码在没有第 60-68 行的情况下编译得很好,但在这些行中会导致编译器错误。

有趣的事情:编译器提示消息注释:

reference must be valid for the static lifetime...

所以编译器想要静态生命周期? (在代码中用 'static 替换 'a 没有帮助)

lifetime_bounds.rs:52:46: 52:56 error: `bitmapdata` does not live long enough
lifetime_bounds.rs:52     let bitmap = PixelImageSimple { pixels: &bitmapdata, width: 222, height:334 };
                                                                   ^~~~~~~~~~
note: reference must be valid for the static lifetime...
lifetime_bounds.rs:50:34: 69:2 note: ...but borrowed value is only valid for the block suffix following statement 0 at 50:33
lifetime_bounds.rs:50     let bitmapdata = vec![1,2,3];
lifetime_bounds.rs:51
lifetime_bounds.rs:52     let bitmap = PixelImageSimple { pixels: &bitmapdata, width: 222, height:334 };
lifetime_bounds.rs:53
lifetime_bounds.rs:54     let sharpen = ImageOperationSharpen { val: 34, bitmapdata: &bitmap };
lifetime_bounds.rs:55     let rotate = ImageOperationRotate { angle: 13.32, bitmapdata: &bitmap };
                      ...

作为替代方法,我尝试了使用集合的解决方案

type CollectionOfImageOperations<'a> = Vec<&'a (ImageOperation<'a> + 'a)>;

但这给了我编译错误,这对我来说不如上面的方法有意义。 (似乎我只能将一个特征对象推送到向量 - 但为什么) - 请参阅 rust-playground 了解代码和错误。

欢迎和赞赏任何提示和技巧。

您 运行 与终生遗漏有冲突。如有疑问,请写出来!

Note: the comments are because what I'm adding here is not valid Rust syntax. Also note that what follows is not entirely accurate, to avoid being bogged down in details.

        fn main () {
            /* Lifetimes come from storage, so we'll annotate relevant
               variables with lifetimes.  We're not defining lifetimes,
               we're just assigning names to the lifetimes that the compiler
               will work out during borrow checking. */
/* 'a: */   let bitmapdata = vec![1,2,3];

            /* We'll also substitute the lifetimes into the types of the
               variables and expressions we talk about.  Again, you normally
               couldn't do this, because you can't name lifetimes *within*
               a function. */
/* 'b: */   let bitmap/*: PixelImageSimple<'a> */
            = PixelImageSimple {
                pixels: &/*'a*/bitmapdata,
                width: 222,
                height: 334
            };

            /* We have to pick *one* lifetime here, so we'll pick the
               "narrowest" lifetime.  We'll cheat here and "assume" that
               'a: 'b (read: "'a outlives 'b"); or, in other words, that
               'b < 'a (read: "'b is no longer than 'a"). */
            let sharpen/*: ImageOperationSharpen<'b> as 'b < 'a */
            = ImageOperationSharpen {
                val: 34,
                bitmapdata: &/*'b*/bitmap/*: PixelImageSimple<'a>*/
            };

            let box_sharpen/*: Box<ImageOperationSharpen<'b>>*/
            = Box::new(sharpen);

            /* We'll introduce `'x` here, because it's not immediately clear
               what this lifetime should be.  The compiler will infer it
               from whatever constraints it's been placed under for us. */
/* 'c: */   let mut image/*: Image<'x>*/
            = Image::new();

            /* Wait, where did `'y` come from?  Lifetime elision.  When
               you're dealing with trait objects, the compiler *must* know
               for how long said object is valid.  Normally, the compiler
               would just look at a type's lifetime parameters, but a trait
               object *throws that information away*.  As a result, it
               needs to preserve this information external to the trait.
               This is done using the `Trait + 'k` syntax, where `'k` is
               a lifetime that bounds *all* possible implementations of
               the trait *in this position*.

               This statement is implicit in the original code, but I'm
               adding it here to make things explicit.  I've also included
               the `impl` to denote how the lifetimes transfer around during
               the cast. */
            let box_sharpen/*: Box<ImageOperation<'b> + 'y>*/
            /*where impl<'l> ImageOperation<'l> for ImageOperationSharpen<'l>*/
            = box_sharpen/*: Box<ImageOperationRotate<'b>>*/
                as Box<ImageOperation/*<'b> + 'y*/>;

            /* The only change here is that I *explicitly* borrow `image`
               in order to make all the lifetimes involved explicit.  In
               addition, we now have all the information necessary to work
               out what the inferred lifetimes above should be. */
            (&/*'c */mut image).add_op(
                box_sharpen/* as Box<ImageOperation<'b> + 'y>*/
            );
            /*where impl<'l> Image::<'l>::add_op<'m>(&'m mut self,
                image_ops: Box<ImageOperation<'l> + 'z>)*/
            /*implies 'l = 'b, 'm = 'c, 'z = 'y, 'x = 'l = 'b*/
        }

检查所有生命周期...'z = 'y 除了。 什么是 'z?不管是什么,它决定了最小值 实现 ImageOperation.

的所有值的生命周期

什么是合理?你说的是 Box 的东西, 那么什么才有意义呢?最短的可能寿命,或 最宽?最窄的几乎会呈现 Box<Trait> 不可用,所以它必须是最宽的。最宽的是'static, 因此 'z = 'static.

但是等等......如果你有 Box<ImageOperation<'q> + 'static>, 实现 ImageOperation<'q> 的类型必须 至少存在 只要 'static 生命周期......这意味着 'q 必须 也'static.

根据这个推理,'x = 'l = 'b = 'static。但这意味着 当我们初始化 sharpen 时,我们使用以下代码 表达式:

bitmapdata: &'static bitmap: PixelImageSimple<'a>

但那是不对的;你不能引用某事 被引用的东西长寿。那意味着我们 require 'a'static... 这意味着 'a 'static.

但是'a是一个栈帧;它 不能 'static!

因此,程序不健全。

...那么如果我们只是明确地告诉编译器我们 不想 + 'static 绑定到我们的特征对象上吗?

struct Image<'a> {
    image_operations: Vec<Box<ImageOperation<'a> + 'a>>
}

impl<'a> Image<'a> {
    fn new() -> Image<'a> {
        Image { image_operations: vec![] }
    }

    fn add_op(&mut self, image_ops: Box<ImageOperation<'a> + 'a>) {
           self.image_operations.push(image_ops);
    }
}

// ...

fn main() {
    // ...
    (&/*'c */mut image).add_op(
        box_sharpen/* as Box<ImageOperation<'b> + 'y>*/
    );
    /*where impl<'l> Image::<'l>::add_op<'m>(&'m mut self,
        image_ops: Box<ImageOperation<'l> + 'z>)*/
    /*implies 'l = 'b, 'm = 'c, 'z = 'y, 'x = 'l = 'b*/
}

现在可以编译了。

Addendum (suggested by aatch): additionally, the lifetime on ImageOperation itself seems misplaced. You aren't using it for anything, and it isn't necessary for the code to work. In that case, you end up dealing with Box<ImageOperation + 'a>s, which is an even better demonstration of why you need trait object lifetime bounds.

我假设您遇到了 XY-Problem(试图找出与您的实际问题无关的问题的解决方案)

Rust 无法推断堆的生命周期。 BoxVec(通过 vec![])分配是堆分配。他们也可以在整个程序中存在,或者只在一个范围内存在。 Rust 对这些分配的生命周期一无所知,除了它们包含的任何引用都需要比您刚刚分配的堆对象更长寿。

您希望多个 ImageOperation 对象具有对 bitmap 对象的引用,并且您希望能够让这些 ImageOperation 对象移动到堆上。最简单的解决方案是摆脱所有引用和生命周期,并结合使用移动和引用计数 Rc 框。

让我们从 PixelImageSimple 类型开始。我们删除了生命周期和引用。

struct PixelImageSimple {
    pixels: Vec<i32>,
    width: i32,
    height: i32,
}

现在您有一个 拥有 一个 Vec 的对象。没有其他对象可以控制 Vec,除非通过控制 PixelImageSimple 对象。

关于 ImageOperation 特征。生命周期 'a 没有出现在特征的主体中。您可以删除它而不会产生任何后果。

trait ImageOperation {
    fn execute_op(&self);
}

现在变得有趣了。您希望 ImageOperationSharpen 类型了解 PixelImageSimple 但其他类型也应该可以访问相同的 PixelImageSimple 对象。这就是引用计数框发挥作用的地方。 Rc 允许多个不可变的 "references" 指向同一个对象,它们都以某种方式拥有该对象。您可以 cloneRc 来创建更多指向同一对象的框。在内部,一个计数器跟踪指向该对象的 Rc 的数量。每当 Rc 被删除(它的作用域结束或您明确调用 drop)时,计数器就会减少。当它达到零时,对象实际上被丢弃并释放内存。

struct ImageOperationSharpen {
    val: i32,
    bitmapdata: Rc<PixelImageSimple>
}

ImageOperation 实现完全相同,只是删除了所有生命周期。

impl ImageOperation for ImageOperationSharpen {
    fn execute_op(&self) {
        println!("ImageOperationSharpen - val = {}, width = {}, height = {}, pixels = {:?}",
            &self.val, &self.bitmapdata.width, &self.bitmapdata.height,&self.bitmapdata.pixels);
    }
}

我们现在为 ImageOperationRotate 重复此操作:

struct ImageOperationRotate {
    angle: f64,
    bitmapdata: Rc<PixelImageSimple>
}

impl ImageOperation for ImageOperationRotate {
    fn execute_op(&self) {
        println!("ImageOperationRotate - angle = {}, width = {}, height = {}, pixels = {:?}",
            &self.angle, &self.bitmapdata.width, &self.bitmapdata.height,&self.bitmapdata.pixels);
    }
}

在这里,我对您要尝试做的事情感到有些困惑。调用execute_op时要修改PixelImageSimple吗?这是不可能的,因为您拥有的引用和 Rc 都不允许修改指向的对象。请参阅此答案底部的解决方案。

struct Image {
    image_operations: Vec<Box<ImageOperation>>
}

impl Image {
    fn new() -> Image {
        Image { image_operations: vec![] }
    }

    fn add_op(&mut self, image_ops: Box<ImageOperation>) {
        self.image_operations.push(image_ops);
    }
}

更改需要一些最小的更改,主要是删除 & 运算符并添加 Rc::new 调用。

fn main () {
    let bitmapdata = vec![1,2,3];

    let bitmap = Rc::new(PixelImageSimple { pixels: bitmapdata, width: 222, height:334 });

    let sharpen = ImageOperationSharpen { val: 34, bitmapdata: bitmap.clone() };
    // since we don't create any more ImageOperations, we can move the
    // Rc directly into this object. otherwise we'd also clone it.
    let rotate = ImageOperationRotate { angle: 13.32, bitmapdata: bitmap };

    let box_sharpen = Box::new(sharpen);
    let box_rotate = Box::new(rotate);

    let mut image = Image::new();

    image.add_op(box_sharpen);
    image.add_op(box_rotate);

    println!("execute_op()");
    for imageops in image.image_operations.iter() {
        imageops.execute_op();
    }
}

相关解决方案

如果您想在每个操作中修改 PixelImageSimple 对象,我会以不同的方式构建所有内容。首先将 ImageOperation 特征的 execute_op 函数更改为也采用 &mut PixelImageSimple.

trait ImageOperation {
    fn execute_op(&self, bitmap: &mut PixelImageSimple);
}

然后从 *ImageOperation 类型中删除所有 Rc<PixelImageSimple>,并向 Image 类型添加一个 PixelImageSimple 字段。

struct ImageOperationSharpen {
    val: i32,
}
impl ImageOperation for ImageOperationSharpen {
    fn execute_op(&self, bitmap: &mut PixelImageSimple) {
        // you could modify bitmap now.
        println!("ImageOperationSharpen - val = {}, width = {}, height = {}, pixels = {:?}",
            self.val, bitmap.width, bitmap.height, bitmap.pixels);
    }
}

struct ImageOperationRotate {
    angle: f64,
}

impl ImageOperation for ImageOperationRotate {
    fn execute_op(&self, bitmap: &mut PixelImageSimple) {
        println!("ImageOperationRotate - angle = {}, width = {}, height = {}, pixels = {:?}",
            self.angle, bitmap.width, bitmap.height, bitmap.pixels);
    }
}

struct Image {
    image_operations: Vec<Box<ImageOperation>>
    bitmap: PixelImageSimple,
}

impl Image {
    fn new(bitmap: PixelImageSimple) -> Image {
        Image {
            image_operations: vec![],
            bitmap: bitmap,
        }
    }

    fn add_op(&mut self, image_ops: Box<ImageOperation>) {
        self.image_operations.push(image_ops);
    }

    fn apply_ops(&mut self) {
        // iterate over the ops and automatically remove them
        for op in self.image_operations.drain() {
            op.execute_op(&mut self.bitmap);
        }
    }
}