Igor Chubin
Igor Chubin

Reputation: 64603

What type should I use for a 2-dimensional array?

What is wrong with the type of a here?

fn foo(a: &[&[f64]], x: &[f64]) {
    for i in 0..3 {
        for j in 0..4 {
            println!("{}", a[i][j]);
        }
    }
}

fn main() {
    let A: [[f64; 4]; 3] = [
        [1.1, -0.2, 0.1, 1.6],
        [0.1, -1.2, -0.2, 2.3],
        [0.2, -0.1, 1.1, 1.5],
    ];
    let mut X: [f64; 3] = [0.0; 3];

    foo(&A, &X);
}

I get the compilation failure:

error[E0308]: mismatched types
  --> src/main.rs:17:9
   |
17 |     foo(&A, &X);
   |         ^^ expected slice, found array of 3 elements
   |
   = note: expected type `&[&[f64]]`
              found type `&[[f64; 4]; 3]`

Upvotes: 11

Views: 9184

Answers (2)

Shepmaster
Shepmaster

Reputation: 431589

Arrays are different types from slices. Notably, arrays have a fixed size, known at compile time. Slices have a fixed size, but known only at run time.

I see two straight-forward choices here (see Levans answer for another). The first is to change your function to only accept references to arrays (or the whole array, if you can copy it or don't mind giving up ownership):

fn foo(a: &[[f64; 4]; 3], x: &[f64; 3]) {
    for i in 0..3 {
        for j in 0..4 {
            println!("{}", a[i][j]);
        }
    }
}

fn main() {
    let a = [
        [1.1, -0.2, 0.1, 1.6],
        [0.1, -1.2, -0.2, 2.3],
        [0.2, -0.1, 1.1, 1.5],
    ];

    let x = [0.0; 3];

    foo(&a, &x);
}

The other easy change is to make your declaration into references:

fn foo(a: &[&[f64]], x: &[f64]) {
    for i in 0..3 {
        for j in 0..4 {
            println!("{}", a[i][j]);
        }
    }
}

fn main() {
    let a = [
        &[1.1, -0.2, 0.1, 1.6][..],
        &[0.1, -1.2, -0.2, 2.3][..],
        &[0.2, -0.1, 1.1, 1.5][..],
    ];

    let x = [0.0; 3];

    foo(&a, &x);
}

Note that this second example, we can use the implicit coercion of a reference to an array to a slice, when we just pass in &a and &x. However, we cannot rely on that for the nested data in a. a has already been defined to be an array of arrays, and we can't change the element type.

Also a word of caution - you really should use the length method of the slice in your ranges, otherwise you can easily panic! if you walk off the end.

fn foo(a: &[&[f64]], x: &[f64]) {
    for i in 0..a.len() {
        let z = &a[i];
        for j in 0..z.len() {
            println!("{}", z[j]);
        }
    }
}

Other stylistic changes I made to meet the Rust style:

  1. variables are snake_case
  2. space after :
  3. space after ;
  4. space around =
  5. space after ,

Upvotes: 17

Levans
Levans

Reputation: 15002

As an alternative to Shepmaster's good explanation on the mechanisms, there is actually another way to have your function accept any mix of arrays and slices (and even Vec): it involves using generics with the AsRef trait.

the idea is to write your function like this:

use std::convert::AsRef;

fn foo<S, T, U>(a: S, x: U)
where
    T: AsRef<[f64]>,
    S: AsRef<[T]>,
    U: AsRef<[f64]>,
{
    let slice_a = a.as_ref();
    for i in 0..slice_a.len() {
        let slice_aa = slice_a[i].as_ref();
        for j in 0..slice_aa.len() {
            println!("{}", slice_aa[j]);
        }
    }
}

This is quite a function, but is in fact quite simple: S must coerce to a &[T] via the AsRef trait, and T must coerce to &[f64] similarly. On the same way U must coerce to &[f64], but we do not necessarily have U == T !

This way, S can be an array of slices, an array of array, a Vec of arrays or of slices, an array of Vec... Any combination is possible as long as the types implement the AsRef trait.

Be careful though: the AsRef trait is only implemented for arrays up to the size 32.

Upvotes: 9

Related Questions