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.
@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}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With