Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lifetimes and references to objects containing references

Let's say I have a struct with a reference in it, and another struct with a reference to that struct, something like this:

struct Image<'a> {
    pixel_data: &'a mut Vec<u8>,
    size: (i32, i32),
}

struct SubImage<'a> {
    image: &'a mut Image<'a>,
    offset: (i32, i32),
    size: (i32, i32),
}

The structs have nearly identical interfaces, the difference being that SubImage adjusts position parameters based on its offset before forwarding to the corresponding functions of the contained Image reference. I would like these structs to be mostly interchangeable, but I can't seem to figure out how to get lifetimes right. Originally, I was just using Image, and could pass around objects simply, without ever mucking about with lifetime specifiers:

fn main() {
    let mut pixel_data: Vec<u8> = Vec::new();
    let mut image = Image::new(&mut pixel_data, (1280, 720));
    render(&mut image);
}

fn render(image: &mut Image) {
    image.rect_fill(0, 0, 10, 10);
}

Then I created SubImage, and wanted to do things like this:

fn render2(image: &mut Image) {
    let mut sub = SubImage {
        image: image,           // line 62
        offset: (100, 100),
        size: (600, 400),
    };

    sub.rect_fill(0, 0, 10, 10);
}

This, however, causes a compiler error:

main.rs:62:16: 62:21 error: cannot infer an appropriate lifetime for automatic coercion due to conflicting requirements

The compiler's suggestion is to change the signature to this:

fn render2<'a>(image: &'a mut Image<'a>)

However, that just pushes the problem up to the function which called render2, and took a &mut Image. And this is quite annoying, as the function calls go a few layers deep, and I didn't have to do any of this when I was just using the Image class (which also has a reference), and adjusting the offsets inline.

So first of all, I don't even understand why this is necessary (admittedly my understanding of rust lifetimes is limited). And secondly (my main question), is there anything I can do to SubImage to make these explicit lifetimes not necessary?

like image 624
Benjamin Lindley Avatar asked Aug 19 '15 07:08

Benjamin Lindley


2 Answers

Yes, this error may be confusing but there is a legitimate reason for it.

struct SubImage<'a> {
    image: &'a mut Image<'a>,
    offset: (i32, i32),
    size: (i32, i32),
}

Here you declare that the reference to Image must live exactly as long as the borrowed data inside the image itself - the same lifetime parameter 'a is used both in the reference and as a parameter for Image: &'a mut Image<'a>.

However, render2() violates this requirement. The actual signature of render2() is as follows:

fn render2<'b, 'a>(image: &'b mut Image<'a>)

Therefore, it tries to create SubImage with &'b mut Image<'a>, where 'b not necessarily equals to 'a (and in this particular case, it most certainly does not), and so the compiler bails out.

Also such signature is the only reason you can call this function while providing it &mut image in main(), because &mut image have lifetime of image variable, but the Image contained inside this variable has lifetime of pixel_data which is slightly longer. The following code is not valid Rust, but it is close to how the compiler understands things and it demonstrates the problem:

fn main() {
    'a: {
        let mut pixel_data: Vec<u8> = Vec::new();
        'b: {
            let mut image: Image<'a> = Image::new(&'a mut pixel_data, (1280, 720));
            render2::<'b, 'a>(&'b mut image);
        }
    }
}

When you declare render2() as

fn render2<'a>(image: &'a mut Image<'a>)

you indeed do "push" the problem upstream - now the function can't be called at all with &mut image, and you can now see why - it would require unifying 'a and 'b lifetimes, which is impossible because 'a is longer than 'b.

The proper solution is to use separate lifetimes for reference to Image and Image itself in SubImage definition:

struct SubImage<'b, 'a:'b> {
    image: &'b mut Image<'a>,
    offset: (i32, i32),
    size: (i32, i32),
}

Now 'b and 'a may be different lifetimes, though in order for this to work you have to bound 'a lifetime with 'b, that is, 'a must live at least as long as 'b. This is exactly the semantics your code needs. If this constraint is not enforced, then it would be possible for the referenced image to "die" before the reference to it goes out of scope, which is a violation of safety rules of Rust.

like image 85
Vladimir Matveev Avatar answered Nov 11 '22 18:11

Vladimir Matveev


is there anything I can do to SubImage to make these explicit lifetimes not necessary?

Vladimir's answer is spot on, but I'd encourage you to change your code a bit. A lot of my original code had very similar references to things with references. If you need that, then having separate lifetimes can help a lot. However, I'd just embed the Image in SubImage:

struct Image<'a> {
    pixel_data: &'a mut Vec<u8>,
    size: (i32, i32),
}

struct SubImage<'a> {
    image: Image<'a>,
    offset: (i32, i32),
    size: (i32, i32),
}

In my case, I wasn't really gaining anything by having nested references. Embedding the struct directly makes it a bit bigger, but can make access a bit faster (one less pointer chase). Importantly in this case, it removes the need for a second lifetime.

like image 3
Shepmaster Avatar answered Nov 11 '22 16:11

Shepmaster