Implementing a generic incrementable trait in Rust

2019-07-13 13:01发布

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.

3条回答
SAY GOODBYE
2楼-- · 2019-07-13 13:36

@Simon Whitehead's example can easily be adapted for stable Rust:

trait Incrementable: Copy + std::ops::AddAssign<Self> {
    fn one() -> Self;

    fn post_inc(&mut self) -> Self {
        self.post_inc_by(Self::one())
    }

    fn post_inc_by(&mut self, n: Self) -> Self {
        let tmp = *self;
        *self += n;
        tmp
    }
}

impl Incrementable for u8  { fn one() -> Self {1} }
impl Incrementable for u16 { fn one() -> Self {1} }
impl Incrementable for u32 { fn one() -> Self {1} }
impl Incrementable for u64 { fn one() -> Self {1} }
impl Incrementable for i8  { fn one() -> Self {1} }
impl Incrementable for i16 { fn one() -> Self {1} }
impl Incrementable for i32 { fn one() -> Self {1} }
impl Incrementable for i64 { fn one() -> Self {1} }
impl Incrementable for f32 { fn one() -> Self {1.0} }
impl Incrementable for f64 { fn one() -> Self {1.0} }

While you need to do the implementation for each type, each of them is extremely simple.

You can also use a macro to hide repetitive implementations:

macro_rules! impl_Incrementable{
    ($($m:ty),*) => {$( impl Incrementable for $m  { fn one() -> Self { 1 as $m } })*}
}

impl_Incrementable!{u8, u16, u32, u64, i8, i16, i32, i64, f32, f64}
查看更多
倾城 Initia
3楼-- · 2019-07-13 13:46

The types that we can increment need to

  1. know the operator and += (AddAssign)
  2. define a value for the "one"-element
  3. be copyable as we want to keep the old un-incremented value.

Point 1. and 3. we can assure by using a trait bound, for point 2. we can set up a trait that has the function one() -> self.

So here is a working example:

// We need to know the operator "+="
use std::ops::AddAssign;

// The trait needs a type parameter
trait Incrementable<T> {
    fn post_inc(&mut self) -> Self;
    fn post_inc_by(&mut self, n: T) -> Self;
}

// We need a trait which tells us the "one" value for a type
trait Increment {
    fn one() -> Self;
}

// We need to implement the Increment trait for every type
// we want to increment.
impl Increment for usize {
    fn one() -> usize {
        1
    }
}

// Finally we implement the Increment trait generically for all types that
// * know the operator "+=" AddAssign
// * are copyable
// * implement our Increment trait, so that we know their "one" value
impl<T: AddAssign + Increment + Copy> Incrementable<T> for T {
    fn post_inc(&mut self) -> Self {
        let tmp = *self;
        *self += T::one();
        tmp
    }

    //"Overload" for full generalizability
    fn post_inc_by(&mut self, n: T) -> 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);
}

You don't have to write an implementation of Incrementable for each type, but you do have to implement the trait that supplies the one() function. You can't get away without that, because for non numerical types it is not obvious what "increment by one" means.

I kept everything in a generic implementation that can be implemented generically. The exception is the T::one(), so no boiler-plate code needed except this one trivial function for each type.

查看更多
时光不老,我们不散
4楼-- · 2019-07-13 13:55

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
    }
}
查看更多
登录 后发表回答