Return a zero filled 2D array

I'm trying to create a function that takes two parameters, how many rows and how many columns and returns a 2D array filled with 0s of type T. This is what I have so far:

pub fn zeros<T>(rows: i32,  cols:  i32) -> Array2D<T>
    where T:std::clone::Clone
{
    return Array2D::filled_with(0 as T, rows as usize, cols as usize);
}

This doesn't work. The compiler gets very mad at me and says: error[E0605]: non-primitive cast: ´i32` as `T. I've tried all sorts of things to make it work but I can't figure it out. I'd appreciate any help.

Well, what should happen if I call zeros::<NonZeroU32>()? That type implements Clone, so it satisfies all listed requirements for the type T, and therefore your function must work with that type.

Introduce a restriction that requires T to have a reasonable zero value, then use that trait method instead of doing 0 as T. For example:

pub fn zeros<T>(rows: i32, cols: i32) -> Array2D<T>
where
    T: std::clone::Clone,
    T: From<u8>,
{
    return Array2D::filled_with(T::from(0u8), rows as usize, cols as usize);
}

There are also crates out there that provide a Zero trait with a method that returns a zero.

3 Likes

Hi and welcome to this forum.

You’re trying to write a generic function in Rust; unlike some other languages, like C++ templates for example, generic functions in Rust are type-checked when defined, not when instantiated. This means that the function definition must specify exhaustively all the properties of the generic type parameter T it needs.

An “unconstrained” type parameter T is rather inflexible, you can only move values around and drop them. You added a T: Clone bound, constraining the type in a way that you can clone the value inside of the function. This T: Clone bound is also what allowed you to call Array2D::filled_with, because that function specifies that it needs T: Clone. (I suppose you just put that bound because the compiler suggested to do so; now you know why it’s there.)

Now how do you further constrain the type so that you can use 0 as T? In fact that’s not quite possible at all, since casting via as is (currently) nothing you can formulate as a bound in a where clause. But you can do a few alternative approaches.

One approach is to limit T to “numeric types”; numeric types should certainly support a value of zero. A way to specify “T is numeric” is not provided in the standard library, but the popular crate num offers a trait like T: Num that might do it. If you use num, you could use the more-specific trait “Zero” for types that have a “zero”-value. You could then do

use num::Zero;
pub fn zeros<T>(rows: i32,  cols:  i32) -> Array2D<T>
    where T: Clone, T: Zero
{
    Array2D::filled_with(T::zero(), rows as usize, cols as usize)
}

Another way to obtain a 0-value for number types in Rust is via the Default trait. It’s a trait for types that have some form of “default value”, and for numbers that’s usually zero. So the definition would become

pub fn zeros<T>(rows: i32,  cols:  i32) -> Array2D<T>
    where T: Clone, T: Default
{
    Array2D::filled_with(T::default(), rows as usize, cols as usize)
}

In the meantime @alice has provided an answer above that involves a “From<u8>” bound. That’s a trait for “types that u8 can be converted into”, which includes almost all primitive numeric types in Rust, with the exception of i8. (Because u8 to i8 conversion can fail.)


Some unrelated tips:

Clone is in the prelude, so you don’t need to write its full path; returning values from functions in Rust is typically done by just writing the expression in the end of the function definition, without a trailing semicolon. I would advise to keep using usize for “number of rows/columns”. There’s no reason to switch to a signed integer type, and also no good reason to use any unsized type other than usize; if you change the argument types to usize, then you can get rid of the casts, “as usize”.


If you use a function like T::default() with T: Default, or T::zero() with T: Zero, you can actually avoid the extra T: Clone bound by using API like Array2D::filled_by_row_major. It’s not a huge win, since number types will typically implement Clone anyways, but I just wanted to note this.

The code would look like this

use num::Zero;
pub fn zeros<T>(rows: usize,  cols:  usize) -> Array2D<T>
where
    T: Zero
{
    Array2D::filled_by_row_major(T::zero, rows, cols)
}
5 Likes

Here's an experiment I made recently: https://docs.rs/zero-one/latest/zero_one/#main

With that you could do

pub fn zeros<T>(rows: i32,  cols:  i32) -> Array2D<T>
    where T: std::clone::Clone + From<Zero>
{
    return Array2D::filled_with(Zero.into(), rows as usize, cols as usize);
}

But the normal thing to use in the community is num_traits::identities::Zero - Rust

Thanks for the help guys. I really appreciate it :slight_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.