Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

lifetime

I am a Rust beginner and I can’t get the following code to compile. What I want is to store several traits in a vector and each of the traits should also have read-only access to a borrowed variable.

I am guessing I have to use „Lifetime bounds“ - like discussed in this thread - because if I comment out lines 60-68, the code compiles fine.

Can somebody please explain how to use „lifetime bounds“ - if this is the way to solve the problem - or is this not the Rust way to solve the problem? If there is a better way to achieve what I’m trying to do I’m glad to change my approach to the problem.

The code which doesn’t compile is here and on 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();
    }
}

I get 3 errors for variable 'bitmapdata' and 'bitmap' twice. As I mentioned above: the code compiles fine without lines 60-68 but results in a compiler error with those lines.

Interesting thing: the compilers hint message note:

reference must be valid for the static lifetime...

So the compiler wants a static lifetime? (replacing 'a with 'static in the code didn't help)

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 };
                      ...

As an alternative approach I tried a solution using a collection

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

but this gave me compile errors which made less sense to me than in the approach above. (It seems like I can only push one trait object to the vector - but why) - see rust-playground for the code and error.

Any hints & tips are welcome and appreciated.

like image 986
bumzack Avatar asked Jul 26 '15 18:07

bumzack


1 Answers

You've run afoul of lifetime elision. When in doubt, write it out!

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*/
        }

All the lifetimes check out... except for 'z = 'y. What is 'z? Whatever it is, it determines the minimum lifetime of all values that implement ImageOperation.

What's reasonable? You're talking about a Box of something, so what would make sense? The narrowest possible lifetime, or the widest? The narrowest would render Box<Trait> almost unusable, so it must be the widest. The widest is 'static, therefore 'z = 'static.

But wait... if you have Box<ImageOperation<'q> + 'static>, the type that implements ImageOperation<'q> must live at least as long as the 'static lifetime... which means that 'q must also be 'static.

By this reasoning, 'x = 'l = 'b = 'static. But this implies that when we initialise sharpen, we do so with the following expression:

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

But that can't be right; you can't have a reference to something that outlives the something being referenced. That means we require 'a to outlive 'static... which means 'a is also 'static.

But 'a is a stack frame; it cannot be 'static!

Thus, the program is not sound.

... so what if we just explicitly tell the compiler that we don't want a + 'static bound on our trait objects?

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*/
}

And it now compiles.

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.

like image 72
DK. Avatar answered Sep 30 '22 06:09

DK.