7

I'm trying to make a Matrix struct and I want to override the Index operator to let me have matrix-style indexing.

For example:

let m = Matrix { ... DATA ... }
let entry = m[0,0]

My structure looks like this:

struct Matrix {
    cols: usize,
    rows: usize,
    data: Vec<f32>
}

I've been looking at the Index trait and I don't see how I can make this work? Additionally I'd like to be able to take ranges in each dimension etc.

1
  • How is this a matrix? A matrix would have a Vec<Vec<f32>> not just one vector. Commented Mar 16, 2017 at 1:47

2 Answers 2

15

Another possibility would be to use 2D-array style indexing like m[0][1]. This is definitely possible -- even quite easy in your case. Your Index implementation just has to return something that is indexable again. Code:

use std::ops::Index;

struct Matrix {
    cols: usize,
    rows: usize,
    data: Vec<f32>
}

impl Index<usize> for Matrix {
    type Output = [f32];
    fn index(&self, index: usize) -> &Self::Output {
        &self.data[index * self.cols .. (index+1) * self.cols]
    }
}

fn main() {
    let m = Matrix {
        cols: 2,
        rows: 2,
        data: vec![1., 2., 3., 4.],
    };

    println!("{} {}", m[0][0], m[0][1]);
    println!("{} {}", m[1][0], m[1][1]);
}

This style is more common among languages like Java and C.

Sign up to request clarification or add additional context in comments.

Comments

13

In short, you cannot do this. The Index trait is defined as:

pub trait Index<Idx: ?Sized> {
    type Output: ?Sized;
    fn index(&self, index: Idx) -> &Self::Output;
}

That is, it takes a single argument of type Idx. The closest you can do is to use a tuple, which is a single type with multiple values packed into it:

impl std::ops::Index<(usize, usize)> for Matrix {
    type Output = f32;

    fn index(&self, idx: (usize, usize)) -> &f32 {
        // or as appropriate for row- or column-major data       
        &self.data[idx.0 * self.cols + idx.1]
    }
}

And it would be called like

matrix[(0, 1)]

bluss points out that the multiarray crate uses two-element arrays instead of a tuple. This is probably easier to type as you can just hit the square brackets twice:

impl std::ops::Index<[usize; 2]> for Matrix {
    type Output = f32;

    fn index(&self, idx: [usize; 2]) -> &f32 {
        // or as appropriate for row- or column-major data       
        &self.data[idx[0] * self.cols + idx[1]]
    }
}

And it's called like matrix[[0, 1]]. The important thing is that there's still just a single value provided as the argument to index.

Repeat the implementation as desired for Range, RangeTo, RangeFrom, and RangeFull. These are all single types, so you can call it like matrix[5..], for whatever that might mean.

8 Comments

Using arrays instead of tuples is a nice trick I saw from multiarray. matrix[[0, 1]] looks good and is easier to type.
@bluss mmm, neat indeed! You want I should add it in here, or do you want to have a separate answer?
Here is fine, I thought it's just a minor detail.
@bluss: I’d do tuples rather than arrays there. Probably my Python background speaking there, where foo[bar, baz] is foo.__getitem__((bar, baz)). (Python does a lot of permitting parenthesis omission with tuples, e.g. foo, bar = baz, foo = bar, baz. Rust doesn’t do any, for now at least.)
@ChrisMorgan: The nice thing about arrays as index is that you can rely on [usize; N] implementing AsRef<[usize]> in generic code to support multiple dimensions more easily. I think if you want to go with tuples you would have to do more special casing for the dimensions. Of course, if you are restricted to 2D, this does not make a difference. But it would be nice to be consistent between different numbers of dimensions. (I'm the author of the multiarray crate, btw)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.