I am new to rust, and I find there seems be many ways to implement traits on objects.
Here is an example:
trait AggFn {
type T;
fn min(&self) -> Self::T;
fn max(&self) -> Self::T;
}
impl AggFn for [f32] {
type T = f32;
fn min(&self) -> Self::T {
*self.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap()
}
fn max(&self) -> Self::T {
*self.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap()
}
}
impl AggFn for [f64] {
type T = f64;
fn min(&self) -> Self::T {
*self.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap()
}
fn max(&self) -> Self::T {
*self.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap()
}
}
Obviously impl AggFn for [f32] and impl AggFn for [f64] is unecessarily duplicated. How can I remove the duplicats by using somethiing like type generic? And I believe there are some more briefer ways to design the trait AggFn to get the same target as been done above, I just can't figure it out.
Something like this is more brief, of course it won't compile:
trait AggFn2<T>
where T: f32 + f64,
{
fn min(&self) -> T {
*self.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap()
}
fn max(&self) -> T {
*self.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap()
}
}
impl AggFn2<f32> for [f32] {}
impl AggFn2<f64> for [f64] {}
While trait-based approaches are also possible, at least the standard library often chose the path of duplicating such impls for primitive numeric types, often using a macro. The macro-based approach would be to write a macro like
A trait-based solutions in this case could work with std-only traits; you probably need to use PartialOrd and Copy. More generally, in more complicated settings (where more capabilities of f32 and f64 are required), the num - Rust crate comes with traits to be generic over numeric types, e. g. num::Float - Rust for floating point types.
Names of crates (as they appear in the list of dependencies in Cargo.toml files) can contain - characters (they can also contain _ directly) but when you use the crate in code, the paths to its items have all the - characters replaced by _, since the former is the subtraction operator and can't be part of identifiers. By the way, crates.io does ensure though that no naming collisions only differentiated by -vs _ exist, and utility commands like cargo add will automatically fix crate names you give in arguments for you with regards to - vs _ typos.
A trait-based approach: you only actually perform two operations that depend on the types f32 and f64 - you're calling .partial_cmp, which is provided by the PartialOrd trait, and you're copying the result implicitly, which is provided by the Copy trait. Your implementation can generalize on those traits:
trait AggFn {
type T;
fn min(&self) -> Self::T;
fn max(&self) -> Self::T;
}
impl<E> AggFn for [E]
where E: PartialOrd + Copy {
type T = E;
fn min(&self) -> Self::T {
*self.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap()
}
fn max(&self) -> Self::T {
*self.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap()
}
}
fn main() {
let f32s = [0_f32, 1_f32];
println!("{}", f32s.min());
println!("{}", f32s.max());
let f64s = [37_f64, 90_f64];
println!("{}", f64s.min());
println!("{}", f64s.max());
}