Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass an arbitrary sized matrix to a function in Rust?

Tags:

types

matrix

rust

I have a 3x3 matrix (a 2D array) passed to a function:

let matrix: [[i32; 3]; 3] = [
    [0, 0, 0],
    [0, 1, 0],
    [0, 0, 0]
];

filter::convolve(&mut image, matrix, 1).unwrap();

The function is currently hardwired to accept 3x3 matrix:

pub fn convolve(src: &mut Image, matrix: [[i32; 3]; 3], divisor: i32) -> Result<&mut Image, String> {
    // ...
}

How would I pass a 3x3, 5x5, or any arbitrarily sized matrix to the same function?

like image 605
kosinix Avatar asked Feb 05 '23 13:02

kosinix


1 Answers

Arrays have a fixed size, determined at compile time. Slices have a fixed size, determined at run-time.

The easiest thing to do is to accept a slice of slices:

fn convolve(matrix: &[&[i32]]) {
    println!("{:?}", matrix);
}

fn main() {
    let matrix = &[
        &[0, 0, 0][..],
        &[0, 1, 0][..],
        &[0, 0, 0][..],
    ];
    convolve(matrix);
}

That's a bit annoying, as you have to use the slicing syntax (&foo[..]) to convert the literal arrays to slices. You could also accept a generic, which allows you to accept the above, but also anything that can be converted to a slice:

fn convolve<T, I>(matrix: &[T])
where
    T: AsRef<[I]>,
    I: std::fmt::Debug,
{
    for part in matrix {
        println!("{:?}", part.as_ref());
    }
}

fn main() {
    let matrix = &[
        [0, 0, 0],
        [0, 1, 0],
        [0, 0, 0],
    ];
    convolve(matrix);
}

As kosinix points out, there is no guarantee that &[&[i32]] will have rows of equal lengths; it's possible to have a ragged array.

The run-time solution to that is to iterate through all the rows and ensure all the lengths are the same. This can be reduced to checking just once if you create a newtype for matrices you have validated:

struct Matrix<'a, T: 'a>(&'a [&'a [T]]);

impl<'a, T> Matrix<'a, T> {
    fn new(slice: &'a [&'a [T]]) -> Result<Self, ()> {
        if slice.is_empty() {
            return Ok(Matrix(slice));
        }

        let (head, tail) = slice.split_at(1);
        let expected = head[0].len();

        if tail.iter().map(|row| row.len()).all(|l| l == expected) {
            Ok(Matrix(slice))
        } else {
            Err(()) // return a real error here
        }
    }
}

Now, whenever you have a Matrix, you can be sure the rows are all the same length.

The compile-time solution... doesn't exist yet. These are called const generics. The proposed syntax would be

fn convolve<const N: usize>(matrix: [[i32; N]; N]) 

There are stable workarounds available (such as generic-array), but these may be limited in one fashion or another.

like image 174
Shepmaster Avatar answered Feb 08 '23 14:02

Shepmaster