rusttraitsgeneric-programming

Implementing a generic incrementable trait in Rust


I'm trying to understand how to implement a generic trait in Rust.

While I've seen a number of examples, the examples are too tied to a specific use (e.g. genomic mutators) for me to be able to understand at this point in my Rust development.

Instead, here's a simple example based on something fairly universal--incrementing:

trait Incrementable {
    fn post_inc(&mut self) -> Self;
    fn post_inc_by(&mut self, n: usize) -> Self;
}

impl Incrementable for usize {
    fn post_inc(&mut self) -> Self {
        let tmp = *self;
        *self += 1;
        tmp
    }

    //"Overload" for full generalizability
    fn post_inc_by(&mut self, n: usize) -> Self {
        let tmp = *self;
        *self += n;
        tmp
    }
}

fn main() {
    let mut result = 0;
    assert!(result.post_inc() == 0);
    assert!(result == 1);

    assert!(result.post_inc_by(3) == 1);
    assert!(result == 4);
}

The above code works, but is lacking because it isn't generalizable to all numeric types without writing a lot of boilerplate code.

In my attempts to generalize the above code, I've gotten into fights with the type system, borrow checker or been forced down a path of implementing FromPrimitive for every type I want to support in my generic version (effectively putting me back to square one).

Can you help me understand how to implement Incrementable generically, such that post_inc() and post_inc_by() work for at least all integer and float types, ideally without having to write an implementation for each type?

I am hoping the answer will help me see how traits, implementations, types and associated types can work together in a more straightforward use case than I've been able to come across.

I'm on Rust 1.16.0.


Solution

  • You could do this with macros, following what the std did:

    trait Incrementable {
        fn post_inc(&mut self) -> Self;
        fn post_inc_by(&mut self, n: Self) -> Self;
    }
    
    macro_rules! post_inc_impl {
        ($($t:ty)*) => ($(
            impl Incrementable for $t {
                fn post_inc(&mut self) -> Self {
                    self.post_inc_by(1 as Self)
                }
    
                fn post_inc_by(&mut self, n: Self) -> Self {
                    let tmp = *self;
                    *self += n;
                    tmp
                }
            }
        )*)
    }
    
    post_inc_impl! { usize u8 u16 u32 u64 isize i8 i16 i32 i64 f32 f64 }
    
    fn main() {
        let mut result = 0;
        assert!(result.post_inc() == 0);
        assert!(result == 1);
    
        assert!(result.post_inc_by(3) == 1);
        assert!(result == 4);
    }
    

    It is possible without macros if you use the num crate:

    extern crate num;
    
    use num::Num;
    
    trait Incrementable<T: Num> {
        fn post_inc(&mut self) -> Self;
        fn post_inc_by(&mut self, n: T) -> Self;
    }
    
    impl<T: Num + std::ops::AddAssign<T> + Copy> Incrementable<T> for T {
        fn post_inc(&mut self) -> T {
            let tmp = *self;
            *self += T::one();
            tmp
        }
    
        fn post_inc_by(&mut self, n: T) -> Self {
            let tmp = *self;
            *self += n;
            tmp
        }
    }