Implimenting traits on Vector

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

macro_rules! agg_fn_float_impl {
    ($T:ty) => {
        impl AggFn for [$T] { ....}
   }
}

and then immediately call it twice

agg_fn_float_impl!(f32);
agg_fn_float_impl!(f64);

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.

2 Likes

Thanks for your help, Float traits works well. Now I can write like this:

use num_traits::Float;

pub trait AggFn<T> {
    fn min(&self) -> T;
    fn max(&self) -> T;
}

impl<T: Float> AggFn<T> for [T] {
    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()
    }
}

What I am confusing is that, trait Float is used in num_traits, but in dependencies, it is named in num-traits, why there exits this defference?

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.

1 Like

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());

}
1 Like