1

I've been working on a multi-dimensional array library, toying around with different interfaces, and ran into an issue I can't seem to solve. This may be a simple misunderstanding of lifetimes, but I've tried just about every solution I can think of, to no success.

The goal: implement the Index and IndexMut traits to return a borrowed vector from a 2d matrix, so this syntax can be used mat[rowind][colind].

A (very simplified) version of the data structure definition is below.

pub struct Matrix<T> {
    shape: [uint, ..2],
    dat: Vec<T>
}

impl<T: FromPrimitive+Clone> Matrix<T> {
    pub fn new(shape: [uint, ..2]) -> Matrix<T> {
        let size = shape.iter().fold(1, |a, &b| { a * b});
        // println!("Creating MD array of size: {} and shape: {}", size, shape)
        Matrix{
            shape: shape,
            dat: Vec::<T>::from_elem(size, FromPrimitive::from_uint(0u).expect("0 must be convertible to parameter type"))
        }
    }
    pub fn mut_index(&mut self, index: uint) -> &mut [T] {
        let base = index*self.shape[1];
        self.dat.mut_slice(base, base + self.shape[1])
    }
}

fn main(){
    let mut m = Matrix::<f32>::new([4u,4]);
    println!("{}", m.dat)
    println!("{}", m.mut_index(3)[0])
}

The mut_index method works exactly as I would like the IndexMut trait to work, except of course that it doesn't have the syntax sugar. The first attempt at implementing IndexMut made me wonder, since it returns a borrowed reference to the specified type, I really want to specify [T] as a type, but it isn't a valid type. So the only option is to specify &mut [T] like this.

impl<T: FromPrimitive+Clone> IndexMut<uint, &mut [T]> for Matrix<T> {
    fn index_mut(&mut self, index: &uint) -> &mut(&mut[T]) {
        let base = index*self.shape[1];
        &mut self.dat.mut_slice(base, base + self.shape[1])
    }
}

This complains about a missing lifetime specifier on the trait impl line. So I try adding one.

impl<'a, T: FromPrimitive+Clone> IndexMut<uint, &'a mut [T]> for Matrix<T> {
    fn index_mut(&'a mut self, index: &uint) -> &mut(&'a mut[T]) {
        let base = index*self.shape[1];
        &mut self.dat.mut_slice(base, base + self.shape[1])
    }
}

Now I get method `index_mut` has an incompatible type for trait: expected concrete lifetime, but found bound lifetime parameter 'a [E0053]. Aside from this I've tried just about every combination of one and two lifetimes I can think of, as well as creating a secondary structure to hold a reference that is stored in the outer structure during the indexing operation so a reference to that can be returned instead, but that's not possible for Index. The final answer may just be that this isn't possible, given the response on this old github issue, but that would seem to be a problematic limitation of the Index and IndexMut traits. Is there something I'm missing?

tshepang
  • 12,111
  • 21
  • 91
  • 136
Tom Scogland
  • 937
  • 5
  • 12

2 Answers2

3

At present, this is not possible, but when Dynamically Sized Types lands I believe it will become possible.

Let’s look at the signature:

pub trait IndexMut<Index, Result> {
    fn index_mut<'a>(&'a mut self, index: &Index) -> &'a mut Result;
}

(Note the addition of the <'a> compared with what the docs say; I’ve filed #16228 about that.)

'a is an arbitrary lifetime, but it is important that it is specified on the method, not on the impl as a whole: it is in absolute truth a generic parameter to the method. I’ll show how it all comes out here with the names 'ρ₀ and 'ρ₁. So then, in this attempt:

impl<'ρ₀, T: FromPrimitive + Clone> IndexMut<uint, &'ρ₀ mut [T]> for Matrix<T> {
    fn index_mut<'ρ₁>(&'ρ₁ mut self, index: &uint) -> &'ρ₁ mut &'ρ₀ mut [T] {
        let base = index * self.shape[1];
        &mut self.dat.mut_slice(base, base + self.shape[1])
    }
}

This satisfies the requirements that (a) all lifetimes must be explicit in the impl header, and (b) that the method signature matches the trait definition: Index is uint and Result is &'ρ₀ mut [T]. Because 'ρ₀ is defined on the impl block (so that it can be used as a parameter there) and 'ρ₁ on the method (because that’s what the trait defines), 'ρ₀ and 'ρ₁ cannot be combined into a single named lifetime. (You could call them both 'a, but this is shadowing and does not change anything except for the introduction of a bit more confusion!)

However, this is not enough to have it all work, and it will indeed not compile, because 'ρ₀ is not tied to anything, nor is there to tie it to in the signature. And so you cannot cast self.data.mut_slice(…), which is of type &'ρ₁ mut [T], to &'ρ₀ mut [T] as the lifetimes do not match, nor is there any known subtyping relationship between them (that is, it cannot structurally be demonstrated that the lifetime 'ρ₀ is less than—a subtype of—'ρ₁; although the return type of the method would make that clear, it is not so at the basic type level, and so it is not permitted).

Now as it happens, IndexMut isn’t as useful as it should be anyway owing to #12825, as matrix[1] would always use IndexMut and never Index if you have implemented both. I’m not sure if that’s any consolation, though!

The solution comes in Dynamically Sized Types. When that is here, [T] will be a legitimate unsized type which can be used as the type for Result and so this will be the way to write it:

impl<T: FromPrimitive + Clone> IndexMut<uint, [T]> for Matrix<T> {
    fn index_mut<'a>(&'a mut self, index: &uint) -> &'a mut [T] {
        let base = index * self.shape[1];
        &mut self.dat.mut_slice(base, base + self.shape[1])
    }
}

… but that’s not here yet.

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • Thank you for the explanation. It does make sense, but I admit I'm surprised that the solution may be found in DSTs. Somehow I was thinking, or perhaps hoping, that there was some way to tie the method's generic lifetime into a the trait definition lifetime. – Tom Scogland Aug 04 '14 at 03:42
  • DST fixes up a few of these sorts of things because `[T]` becomes a valid type where a generic `&U` or `&mut U` will then work for `U = [T]`, producing `&[T]` or `&mut [T]`. It takes the reference lifetime problem out of the way. – Chris Morgan Aug 04 '14 at 11:06
  • @ChrisMorgan: Is it not possible to remove the dependency on DST here by wrapping the result in another type (which would be parameterized by a lifetime and contain a slice) ? For a `Matrix` I could imagine returning a `RowMut<'a, T>` type for example, which would itself implement `IndexMut`. – Matthieu M. Aug 06 '14 at 12:52
  • @MatthieuM. I don’t see how that would help. Elucidate? – Chris Morgan Aug 06 '14 at 15:18
  • @ChrisMorgan: Sorry, I just noticed the issue. I was thinking of using a proxy (`Row`), but the specification of the return type of `IndexMut::index_mut` precludes it. This is very inconvenient. – Matthieu M. Aug 06 '14 at 15:45
  • @ChrisMorgan: For information, [this is what I would have liked](http://is.gd/cdrahd). I feel that preventing using proxies will unduly restrict the use of proxies (and thus views), was there any discussion over this interface ? – Matthieu M. Aug 06 '14 at 16:41
  • @MatthieuM. you could have `trait Index<'a, Index, Result> { fn index(&'a self) -> Result; }`, but that would damage the intrinsic semantics of indexing. (a) You could no longer guarantee based solely on observing `a[b]` what the lifetime of that expression would be. (b) It would also wreak havoc on `IndexMove` versus `Index` versus `IndexMut`—they would need to be rethought and reimplemented considerably differently, and I don’t think the outcome would be positive. – Chris Morgan Aug 07 '14 at 01:25
2

This code works in Rust 1.25.0 (and probably has for quite a while)

extern crate num;

use num::Zero;

pub struct Matrix<T> {
    shape: [usize; 2],
    dat: Vec<T>,
}

impl<T: Zero + Clone> Matrix<T> {
    pub fn new(shape: [usize; 2]) -> Matrix<T> {
        let size = shape.iter().product();

        Matrix {
            shape: shape,
            dat: vec![T::zero(); size],
        }
    }

    pub fn mut_index(&mut self, index: usize) -> &mut [T] {
        let base = index * self.shape[1];
        &mut self.dat[base..][..self.shape[1]]
    }
}

fn main() {
    let mut m = Matrix::<f32>::new([4; 2]);
    println!("{:?}", m.dat);
    println!("{}", m.mut_index(3)[0]);
}

You can enhance it to support Index and IndexMut:

use std::ops::{Index, IndexMut};

impl<T> Index<usize> for Matrix<T> {
    type Output = [T];

    fn index(&self, index: usize) -> &[T] {
        let base = index * self.shape[1];
        &self.dat[base..][..self.shape[1]]
    }
}

impl<T> IndexMut<usize> for Matrix<T> {
    fn index_mut(&mut self, index: usize) -> &mut [T] {
        let base = index * self.shape[1];
        &mut self.dat[base..][..self.shape[1]]
    }
}

fn main() {
    let mut m = Matrix::<f32>::new([4; 2]);
    println!("{:?}", m.dat);
    println!("{}", m[3][0]);

    m[3][0] = 42.42;
    println!("{:?}", m.dat);
    println!("{}", m[3][0]);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366