What are generics?
Generics let you write code that works with many types, not just one.
Instead of writing:
#![allow(unused)] fn main() { struct RidgeEstimator { beta: f64, } }
You can write:
#![allow(unused)] fn main() { use num_traits::Float; struct RidgeEstimator<F> { beta: F, } }
Here, F is a type parameter: it could be f32, f64, or another type. In Rust, generic types have no behavior by default.
#![allow(unused)] fn main() { fn sum(xs: &[F]) -> F { xs.iter().sum() // This will not compile } }
The compiler gives an error: "F might not implement Sum, so I don’t know how to .sum() over it."
Trait bounds
To fix that, we must tell the compiler which traits F should implement.
For example:
#![allow(unused)] fn main() { use num_traits::Float; use std::iter::Sum; impl<F: Float + Sum> RidgeModel<F> for GenRidgeEstimator<F> { ... } }
This means:
Fmust implementFloat(it must behave like a floating point number: supportpowi,abs, etc.)Fmust implementSum(so we can sum an iterator ofF)
This allows code like:
#![allow(unused)] fn main() { let mean = xs.iter().copied().sum::<F>() / F::from(xs.len()).unwrap(); }
Using generic bounds allows the estimator to work with f32, f64, or any numeric type implementing Float. The compiler can generate specialized code for each concrete type.