6
votes

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.

2
it's because rust cannot reason about heap lifetimes. Just put your bitmapdata vector into PixelImageSimple by value and put the bitmap into an Rc and then clone the Rc to pass the cloned version by value into the other objects. is.gd/QIVXaNoli_obk
@ker cool solution, but why do you not clone bitmap when creating the sharpen object?Syntactic Fructose
because I don't need bitmap after that. so I can just move it out of the function. If you still need it afterwards, clone it and move the clone into there. Note: cloning the Rc does nothing but increase the reference counter. No data is copied.oli_obk
@ker: thanks a lot for your quick response and solution. really appreciate your time and help!bumzack

2 Answers

14
votes

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.

13
votes

I'm assuming you have encountered the XY-Problem (Trying to figure out the solution to an issue that is unrelated to your real problem)

Rust cannot reason about heap-lifetimes. The Box and Vec (through vec![]) allocations are heap allocations. They could just as well live for the entirety of the program, or just for a single scope. Rust does not know anything about the lifetime of these allocations, except that any references they contain need to outlive the heap object you just allocated.

You want multiple ImageOperation objects to have a reference to the bitmap object, and you want to be able to let those ImageOperation objects be moved onto the heap. The simplest solution is to get rid of all references and lifetimes and use a combination of moving and reference counted Rc boxes.

Lets start with the PixelImageSimple type. We remove the lifetimes and references.

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

now you have an object that owns a Vec. No other object can control that Vec except through controlling the PixelImageSimple object.

Onto the ImageOperation trait. The lifetime 'a doesn't show up in the body of the trait. You can remove it without consequences.

trait ImageOperation {
    fn execute_op(&self);
}

Now it gets interesting. You want the ImageOperationSharpen type to know about the PixelImageSimple but other types should also have access to the same PixelImageSimple object. This is where reference counted boxes come into play. An Rc allows multiple immutable "references" to the same object which all own the object in a way. You can clone an Rc to create more boxes pointing to the same object. Internally a counter keeps track of the number of Rc pointing to that object. Whenever an Rc gets dropped (its scope ends or you explicitly call drop) the counter is decreased. When it reaches zero, the object is actually dropped and the memory is freed.

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

The ImageOperation implementation is exactly the same, just with all lifetimes removed.

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

We now repeat this for 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);
    }
}

Here I'm a little confused as to what you are trying to do. Do you want to modify the PixelImageSimple when you call execute_op? This isn't possible as both the references you had and the Rc don't allow modification of the pointed-to object. See at the bottom of this answer for a solution.

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

The changes require some minimal changes, mostly removal of the & operator and adding the Rc::new call.

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();
    }
}

A related solution

If you want to modify the PixelImageSimple object in every operation, I'd structure everything a little differently. First change the ImageOperation trait's execute_op function to also take an &mut PixelImageSimple.

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

Then remove all the Rc<PixelImageSimple> from the *ImageOperation types, and instead add a PixelImageSimple field to the Image type.

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