Lifetimes of references inside structures
By: George Shuklin
Tags: Type Systems, Lifetimes, Learning To Code, Rust, Associated Types

Brief introduction: I’m trying to create a good way to work with an image, defined by two sets of coordinates: pixel-level (integers), and Cartesian (floating points, with dx , dy for the size of a pixel in those Cartesian coordinates). Meanwhile the actual values are stored in img: Vec for performance reasons. My first approach was to have i, j (pixel coordinates) pointing to somewhere in img array. Then I though: Oh, I don’t need them, all I need is index in img . Then I though: Oh, I don’t need index, I can keep a pointer to value inside img with values. I will put them to structure ‘Pixel’ with information on Cartesian coordinates and use it as primitive for pixel manipulations.

…And lifetimes story has started.

Rust said I can’t do this without lifetime annotations. I can’t just ‘reference to somewhere in the heap’. I know Rust well enough to understand the problem. There is a reference. How long this reference should live?

Moreover, any my attempt to implement it, will lead to another question: if you borrow your img to some reference to one of it’s values, is it a mutable reference? And if it is, that means ‘no more access to it until this borrow expires’.

After I realized all that, I understood, that this way more complicated than I thought. And while my code can survive with index, my primary goal is to learn Rust. So, fight for the sake of the good fight!

Problem #1: I can’t make my Pixel structure (with all those ‘many coordinates’ things) without explicit relation with img vector. I already have Canvas structure which have that img , so, here is an idea (solution?) #1: Pixel should live no longer than img , that means, no longer than Canvas . The best way to assure this, is to produce Pixel ’s inside of Canvas functions. Iterator sounds great, actually.

So I can declare my lifetime now:

struct Pixel<'img>{

value: &'img u32,

x: f64, //Cartesian coordinates

y: f64,

dx: f64, //Size of pixel in Cartesian

dy: f64

}

I use 'img as lifetime name. I don’t know if this a good idea or not. My python experience says me: the more semantic is in object names, the better, so I couldn’t resist to put some semantic in lifetime name. Meanwhile I know perfectly that the usual lifetime names are one-letter, and they starts from ‘ 'a ’.

It was simple. Now the hard part, which, again, pointing to this tangle of Iterators, traits, associated types and lifetimes, which is the core part of Rust hardness.

I’m glad I stumble on this in a simple form. Instead of trying my way around, I’ll dig directly into this problem.

Let’s simplify it a bit:

struct Pixel {

value: &mut u32,

x: usize,

y: usize

} struct Canvas {

img: Vec<u32>,

max_x: usize,

max_y: usize,

}

I need to implement an iterator for Canvas which returns a next Pixel in the picture. Pixel should contain x,y coordinates and mutable reference to value of the pixel inside of the img array.

Notice to a reader: this is my learning blog. I can try stupid and incorrect attempts here based on my wrong (and eventually, I hope, corrected) assumptions. Moreover, my language may wobble between present and past as I writing this in chunks. This is like ‘laboratory log’, not a concise wringing. My apology to affected.

I start from things I understand.

This code works, with lifetimes and so on:

struct Pixel<'img> {

value: &'img mut u32,

x: usize,

y: usize

} struct Canvas {

img: Vec<u32>,

max_x: usize,

max_y: usize,

} fn get_pixel<'a>(canvas: &'a mut Canvas, x: usize, y: usize) -> Pixel<'a>{

assert!(x<canvas.max_x);

assert!(y<canvas.max_y);

Pixel{value: &mut canvas.img[y*canvas.max_y+x], x: x, y: y}

} fn main(){

let mut canvas = Canvas{img:vec![0;9], max_x: 3, max_y:3};

let pixel=get_pixel(& mut canvas, 1, 1);

println!("{:?} at ({:?}, {:?})", pixel.value, pixel.x, pixel.y);

}

get_pixel uses lifetimes to say that return value must live at most (this is mut!) as the input value.

It’s a mut borrow, so I suspect I can’t have second pixel here.

Yes, I’m right:

22 | let pixel2=get_pixel(& mut canvas, 0, 0);

| ^^^^^^^^^^^^ second mutable borrow occurs here

So far so good. Before moving to iterators, let’s do this in loop. This code should work in my understanding:

for i in 0..3 {

let pixel=get_pixel(& mut canvas, i as usize, 1);

println!("{:?} at ({:?}, {:?})", pixel.value, pixel.x, pixel.y);

}

And it is. One more check of my intuition: If I change value in Pixel, it should be saved in original Canvas.

for i in 0..3 {

let pixel=get_pixel(& mut canvas, 1 as usize, 1);

* pixel.value = i;

}

for _ in 0..3 {

let pixel=get_pixel(& mut canvas, i as usize, 1);

println!("{:?} at ({:?}, {:?})", pixel.value, pixel.x, pixel.y);

}

}

Yep, ignoring few minor bugs, it works as expected.

Now let’s try to create and iterator for Canvas. I will use one raw for simplicity. As there is no yield in Rust yet, and I don’t want to play with nested iterators yet, I need to keep my state somewhere. All fancy tricks aside, I’ll use a simple counter in ‘Canvas’ for a single-time iteration. I want to focus on lifetimes, so please forgive me for bugs in iterator logic.

impl Iterator for Canvas {

fn next(&'a mut self) -> Pixel<'a>{

let retval = Pixel{value: &mut self.img[self.iter_count], x: 0, y: 0};

self.iter_count+=1;

return Some(retval);

}

}

It does not compile:

error[E0261]: use of undeclared lifetime name `'a`

--> src/main.rs:21:14

|

21 | fn next(&'a mut self) -> Pixel<'a>{

| ^^ undeclared lifetime error[E0261]: use of undeclared lifetime name `'a`

--> src/main.rs:21:36

|

21 | fn next(&'a mut self) -> Pixel<'a>{

| ^^ undeclared lifetime

I need to declare my lifetimes somewhere outside of the function. impl is a good?

I followed compilers errors and made this:

impl<'a> Iterator for Canvas {

type Item=Pixel<'a>;

fn next(&'a mut self) -> Pixel<'a>{

let retval = Pixel{value: &mut self.img[self.iter_count], x: 0, y: 0};

self.iter_count+=1;

return Some(retval);

}

}

It does not compile with a new error (“unconstrained lifetime parameter”) I barely understand what’s going on.

I need to read a chapter on associated types. (..reading… yep, chapter 19 is discussing exactly my case… continue to read).

Nope, they never talked about combination of lifetime and associated types. Huh. Has I stepped onto wasp’s nest?

To make my code to have less logic I trimmed it down to this (still non-compilable) version, and posted it into user group. The problem is harder than I anticipated.

Update: it’s impossible

I got (so far) two answers, both pointing that my goal is not achievable … wait, there is a third answer.

Let’s read them thoroughly.

We start from RFC, which discuss Generic Associated Types with implications for ‘stream iterator’ (apparently, it’s the thing I’m trying to implement above).

Huh. I got introduced to idea of type kind. The kind of the type as a special term. It’s harsh. I can follow the reasoning in RFC, but superficially.

Next, is a piece of working code I want to soak into (in the next article).

And, finally, there is a streaming_iterator crate, which I will play with too.