In Rust, a function that sums the elements of a given slice looks like this:
fn sum(slice: &[u32]) -> u32 {
return slice.iter().sum()
}
and the main function can simply call it like so:
fn main() {
println!("Sum: {}", sum(&[1, 2]));
}
But, what if we need to sum slices of different types (u32, u64, i32, etc.)?
We could define our function for each type, but as you can imagine, this would be hard to maintain and test.
Intro to Generics
To solve this, Rust provides a way to define generic functions that could be defined once but used for multiple types.
In our case, we can define a single function that can sum any slice. So, you would probably expect the function to be like so:
fn sum<T>(slice: &[T]) -> T {
return slice.iter().sum()
}
Essentially, we are defining a generic function over `T` that accepts a slice of elements of type `T` and returns the summation which is also of type `T`.
However, as you would probably expect, the compiler would fail here as it needs to know whether the elements of the provided type can actually be summed over.
Trait Bounds
In order to solve this, Rust has a concept of trait bounds, where you can specify constraints on the generic type that a generic function can accept.
In our case, we can utilize a trait `Sum` in `std::iter` and the trait `Copy` to be able to copy the result. Thus, our generic function would actually look like the following:
use std::iter::Sum;
fn sum<T: Sum<T> + Copy>(slice: &[T]) -> T {
return slice.iter().copied().sum()
}
Auto-inference of types in Rust makes it possible to use the generic function directly without even specifying the concrete type. Hence, our main function still looks like the above, but can now be called for all types that implement `Sum` and `Copy`:
fn main() {
println!("Sum: {}", sum(&[1, 2]));
}