2
votes

I've run into a problem implementing Index on a type with a lifetime constraint. I have a SubImage struct which contains a reference to an Image. I can't find any way to satisfy the compiler.

    impl<'a, P> Index<usize> for SubImage<'a, P> {
      type Output = [P];

      fn index<'b> (&'b self, y: usize) -> &'b [P] {
        let start = (self.rect.y0 + y) * self.image.size.x + self.rect.x0;
        let end = start + self.rect.width();
        &self.image.buf[start..end]
      }
    }

'a is the lifetime of the referenced image, so slicing its buffer requires this constraint. The code here compiles, but it is ambiguous. All calls to the index operator result an an error message such as:

    src/image.rs:179:13: 179:32 error: cannot infer an appropriate lifetime for lifetime parameter 'a in function call due to conflicting requirements
    src/image.rs:179       Some(&self.sub[self.back])
                                 ^~~~~~~~~~~~~~~~~~~
    src/image.rs:174:3: 181:4 help: consider using an explicit lifetime parameter as shown: fn next_back(&'a mut self) -> Option<&'a [P]>
    src/image.rs:174   fn next_back (&mut self) -> Option<&'a [P]> {
    src/image.rs:175     if self.front == self.back {
    src/image.rs:176       None
    src/image.rs:177     } else {
    src/image.rs:178       self.back -= 1;
    src/image.rs:179       Some(&self.sub[self.back])

Is there any possible way to ensure that the return value is constrained to both 'a and 'b or some other way to implement Index properly in a situation like this? The compiler's suggestion doesn't work because the function signature doesn't match that of the trait.

1

1 Answers

6
votes

There's actually more to your problem and it is not the Index trait implementation. Also, your example is not really a minimal, complete, and verifiable example (MCVE), so I have to guess as to what your exact problem is here.

The core of your problem is, that you cannot have your iterator return a reference, if it owns the content without borrowing the iterator itself. Your implementation of the Index trait for you SubImage is fine.

I will try to simulate your problem. Let's say we have a struct Julmond and it borrows some slice of integers (similar to your SubImage).

struct Julmond<'a>(&'a [i32]);

impl<'a> Index<usize> for Julmond<'a> {
    type Output = [i32];

    fn index<'b>(&'b self, idx: usize) -> &'b [i32] {
        if idx < self.0.len() {
            &self.0[idx..] // we always take a subslice from idx until the end
        } else {
            panic!("Index out of bounds!")
        }
    }
}

The Index trait requires that we borrow self. That is fine, since some implementors might own the data you're indexing into. This borrowing of self is expressed by linking the named lifetimes of self and the outgoing reference in the traits method signature:

fn index(&'a self, index: Idx) -> &'a Self::Output;

If we index into a Julmond, that value is considered borrowed as long as we hold on to the resulting reference into the Julmond:

let array = [1, 2, 3, 4, 5, 6];
let mut j = Julmond(&array);

let r = &j[3];

&mut j; // Error: r is still in scope and therefore j is still borrowed

What I can read from your example code is that you have some type that owns your SubImage and implements the Iterator trait. We will try to mimic that with another struct Nebelung implementing the Iterator trait on the way:

struct Nebelung<'a> {
    j: Julmond<'a>,
    pos: usize,
}

impl<'a> Iterator for Nebelung<'a> {
    type Item = &'a [i32];

    fn next(&mut self) -> Option<&'a [i32]> {
        if self.pos < self.j.0.len() {
            let tmp_pos = self.pos;
            self.pos += 1;
            Some(&self.j[tmp_pos]) // problematic line
        } else {
            None
        }
    }
}

This implementation returns an ever shrinking slice of the array from the underlying Julmond struct. We can test it like this:

fn main() {
    let array = [1, 2, 3, 4, 5, 6];
    let j = Julmond(&array);
    let n = Nebelung { j: &j, pos: 0 };

    for s in n {
        println!("{:?}", s);
    }
}

But this doesn't work. The compiler will complain (like in your example) that it cannot infer an appropriate lifetime for 'a. The reason is the borrow of self in the index method. When we call the index operator with j[tmp_pos] we are borrowing j. But j is owned by self of type Nebelung and so borrowing from j means we are borrowing from self. We are trying to return a reference to something that is owned by self and that requires that self has to be borrowed as well. The compiler suggests the right thing: linking the lifetimes of self and the outgoing reference. However, this violates the method signature of next.

If we want to return a reference from an iterator, that iterator cannot own the returned value. Otherwise, we would have to borrow the iterator in the call but that is not possible with next.

The only way around this, is having the iterator NOT own the value. So we modify the Nebelung struct to hold a reference to a Julmond:

struct Nebelung<'a: 'b, 'b> {
    j: &'b Julmond<'a>,
    pos: usize,
}

The 'a: 'b means that "'a outlives 'b" and it is required here. Since our reference j to a Julmond must not outlive the borrowed content of the Julmond. Ok great, our Nebelung is not the owner of the Julmond anymore. Just a borrower. Now we can implement the Iterator trait for it like this:

impl<'a, 'b> Iterator for Nebelung<'a, 'b> {
    type Item = &'b [i32];

    fn next(&mut self) -> Option<&'b [i32]> {
        if self.pos < self.j.0.len() {
            let tmp_pos = self.pos;
            self.pos += 1;
            Some(&self.j[tmp_pos])
        } else {
            None
        }
    }
}

The lifetimes of self and the outgoing reference are not required to be linked, since we are just returning a reference to some value which we are not the owner of. So the call to &self.j[tmp_pos] is not a borrow from self anymore. It is a borrow from the Julmond (via the index implementation).

Complete example

Whatever type you are implementing the Iterator trait for. You cannot have next (or next_back) return a reference if the type owns the value. Have your type borrow the SubImage instead.