How do I make public function that accept a fixed size array?

Hi everyone, I got a problem using rust while learning. My requirement is that I want to implement a function that takes an array and adds all of its contents by one . To enhance the reusability of the code, I want this function to accept arrays of any size

For example ,something like the following psuedo code

fn add_one(lst:&mut Vec<[i32;n]>){
    for i in 0..lst.len() {
        ... // add one for each
    }
}

fn main() {
    let mut arr1:Vec<[i32;2]> = Vec::new();
    arr1.push([1,2]);
    arr1.push([3,4]);
    add_one(&arr1);
    let mut arr2:Vec<[f32;3]> = Vec::new();
    arr2.push([1.0,2.0,3.0]);
    arr2.push([4.0,5.0,6.0]);
    add_one(&arr2); 
    
    // expect arr1 be modified into [[2,3],[4,5]]
    // and arr2 into [[2.0,3.0,4.0] , [5.0,6.0,7.0]]
}

I'm tried to use some simple generics , however it didn't work. What should I do?

fn add_one <T: ExactSizeIterator> (lst:&[T]){
    for item in lst.iter() {
        println!("{:?}" , item.len());
    }
}

You want to use const generics

fn add_one<const N: usize>(lst:&mut Vec<[i32;N]>){
    for i in 0..N {
        
    }
}
2 Likes

You could use AsMut<[i32]> instead of const generics, and if you want the numeric type to be generic you'll have to use traits for that too, like:

use num_traits::One;
use std::ops::AddAssign;

fn add_one<T, A>(lst: &mut [A])
where
    T: AddAssign + One,
    A: AsMut<[T]>,
{
    for array in lst {
        for x in array.as_mut() {
            *x += T::one();
        }
    }
}
5 Likes

Thanks for reply!

It shows that "const generics are unstable" and the compiler doesn't allow me to use it.

Edit:

Thanks for reply , however as a beginner, I still can't understand what this code has done very well . Seems like the trait AddAssign allows me to use += operator . I tried not to implement the traits(just simply delete related codes) , the final code shows like this:

use std::ops::AddAssign;

fn add_one<T, A>(lst: &mut [A])
where
    T: AddAssign + Copy + std::fmt::Display + std::fmt::Debug,
    A: AsMut<[T]>,
{
    for i in 0..lst.len() {
        for x in lst[i].as_mut() {
            *x += *x;
        }
    }
}

fn main() {
    // let mut arr1:Vec<[i32;2]> = Vec::new();
    // arr1.push([1,2]);
    // arr1.push([3,4]);
    // add_one(&mut arr1);
    let mut arr2:Vec<[f32;3]> = Vec::new();
    arr2.push([1.0,2.0,3.0]);
    arr2.push([4.0,5.0,6.0]);
    arr2.push([7.0,8.0,9.0]);
    add_one(&mut arr2);
    println!("{:?}" , arr2)
}
}

The code could work ,get the result [[2.0, 4.0, 6.0], [8.0, 10.0, 12.0], [14.0, 16.0, 18.0]], however there's still some technical difficulties to me , first is ,what if I'd like to unify them all with a constant number , for example I want *x = 1; , obviously it's not going to work cause input type could be both float and int , and the compiler raised error message:

cannot add `{integer}` to `T`rustc(E0369)
main.rs(10, 19): T
main.rs(10, 24): {integer}

Secondly, I would like to achieve a gap-filling function , for example I got a 3x3 matrix as follow:

[
   [0,2,2],
   [3,3,3],
   [4,4,4],
]

Because I'm not sure if there are zeros in the matrix , if any zeros appear(in this perticular case ,the top left corner is zero) , I'd like to have the column below it shift up to filll it. For example get a result like this:

// after process
[
   [3,2,2],
   [3,3,3],
   [4,4,4],
]

In the traditional way of writing in other languages , the pesudo code may like this

for row_index in 0..arr.len() {
    for col_index in 0..arr[row_index].len() {
        if arr[row_index][col_index] == 0 {
            arr[row_index][col_index] = arr[row_index+1][col_index];
        }
    }
}

But because you are using AsMut in the code above , if I'm trying to use index access(arr[i][1]) , the compiler gives an error message:

&mut [A]
cannot index into a value of type `A`rustc(E0608)

const generics will become stable in Rust 1.51, which will be released on 2021-03-25 (about three weeks from now). You can use them today in the beta and nightly toolchains.

3 Likes

Well AsMut itself isn't indexable, but it tells you that you can make a mutable slice, and you can index into that (arr.as_mut()[i][j]).

You can add an additional From<i16> bound on your type, for example, and then you can make small literals using *x = T::from(1). I believe the largest type for which this works is i16, for which both f32 and larger integer types implement From.

For a more elegant and general solution, see numeric traits defined in num_traits, e.g. Zero and One.

2 Likes

You don't need const generics for this at all:


fn add_one(outer: &mut [impl AsMut<[i32]>]) {
    for slice in outer {
        let slice = slice.as_mut();
        for inner in slice {
            *inner += 1;
        }
    }
}

However, for abstracting away difference between integer and float types you will need either a bunch of std::ops::* trait bounds, or something like num-traits.

I strongly advise against trying to write code that is abstract over numeric type. You will need tons of trait bounds for every tiny detail, including nitpicking about types of outputs of each arithmetic operation. In non-trivial functions it quickly gets to the point of being unreasonably complicated, messy and unreadable. This is because Rust doesn't have a concept of a generic number, so it will try force you to make your numeric functions also work any hypothetical non-numeric weird type that could implement all of these traits. What if someone wrote struct Duck that adds up to a struct Horse? If you don't exclude that possibility with a trait bound, that's what you gotta support!

Just pick a type that works for you, maybe a type alias if you want to try the code with different sizes before settling on a single non-generic one.

And if you really really need to have a version of this function for every numeric type, then use macros to generate multiple functions.

6 Likes

Thank you for the design principles, I wouldn't set this answer to the solution because it doesn't fit as well with the original title, but it is perhaps the best practice practicality & in terms of design philosophy , I will organize my code as you suggested.

1 Like