Passing generic vector of numbers

I'm learning Rust and want to understand how I can pass a vector of numbers (any kind) to a function. This code does not compile, but I can't figure out how to fix it:

use std::ops::{Add, Div};

fn average<T: Add + Div>(numbers: &Vec<T>) -> T {
  let sum: T = numbers.iter().fold(0, |acc, n| acc + n);
  sum / numbers.len()
}

fn main() {
  let numbers: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0];
  println!("average2 = {}", average(&numbers));
}

I understand I can use numbers.iter().sum(), but I want to learn about fold.
The compiler complains about:

  • zero as the first argument to fold
  • acc + n
  • the return expression in the average function

The T type might not be an integer, so you can't use 0.

1 Like

You could look into the num-traits crate.

use num_traits::cast::FromPrimitive;
use num_traits::identities::{zero, Zero};
use std::ops::Div;

fn average<T: Zero + Div<Output = T> + Clone + FromPrimitive>(
    numbers: &[T],
) -> T {
    let sum: T = numbers.iter().cloned().fold(zero(), |acc, n| acc + n);
    sum / T::from_usize(numbers.len()).unwrap()
}

fn main() {
    let numbers: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0];
    println!("average2 = {}", average(&numbers));
}

(playground)

You can use Default trait to get zero value (most of the time). See also num-traits for more traits you can use.

When you say it's a type T: Add + Div Rust assumes it can be any type that implements them, including types created in the future that don't exist yet. It could be a matrix. It could be a string. It could be an image. There is nothing that limits Add or Div to numbers.

Thanks steffahn! Is this the simplest way to write a function that computes the average of a list of numbers in Rust or is there a better approach?

Thanks kornel! I tried changing my average function to this:

fn average<T: num_traits::Num>(numbers: &Vec<T>) -> T {
  let sum: T = numbers.iter().fold(Default::default(), |acc, n| acc + n);
  sum / numbers.len()
}

It's unhappy with my use of Default::default() and numbers.len().
I'm not sure how to fix it from here.

You have to declare absolutely every operation you could possibly use on the type, so use T: Num + Default to have Default available.

len() gives you usize, but there's no guarantee that T can be divided by usize. Rust doesn't have implicit numeric conversions, so only usize can be divided by usize.

Theoretically you can add T: From<usize> and use T::from(numbers.len()), but due to portability concerns very few data types can be converted from usize.

Instead of returning T you could return f64 and use num-traits' trait for casting types to f64 and divide it as a concrete type.

But Rust's generics are a very poor fit for numeric code. On every step Rust will make you ask "but what if the T is an array or a string or a file handle? How would I divide a friggin file handle by usize!?"

  • Consider using macros. They're not type-safe and closer to templates in C++, so you don't have to declare as much (but you will still need to cast usize to whatever type you divide)
  • Consider using concrete types. Do you really need to support every possible type?
  • Consider using type aliases, e.g. type MyNum = f64. That makes it slightly easier to switch to another type if you change your mind later.
3 Likes