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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With