How to implement variable increment via side effect?

5.2k views Asked by At

For learning purposes, I tried this solution, but it does not work:

use std::ops::Add;

fn inc<T: Add>(x:&mut T) {
    *x += 1; 
}

fn main() {
    let mut x:i32 = 10;
    let mut y:u8 = 1;
    inc(&mut x);
    inc(&mut y);
    println!("{} {}", x, y);
}

Error message:

<anon>:4:5: 4:7 error: binary assignment operation `+=` cannot be applied to types `T` and `_` [E0368]
<anon>:4     *x += 1; 
             ^~
<anon>:4:5: 4:7 help: see the detailed explanation for E0368
error: aborting due to previous error

What is the right way to do that?

1

There are 1 answers

1
Chris Morgan On BEST ANSWER

At present, += is only defined on the primitive integer types; generically, you will need to expand it to *x = *x + 1; instead. This then reveals more problems:

<anon>:4:15: 4:16 error: mismatched types:
 expected `T`,
    found `_`
(expected type parameter,
    found integral variable) [E0308]
<anon>:4     *x = *x + 1; 
                       ^
<anon>:4:10: 4:16 error: mismatched types:
 expected `T`,
    found `<T as core::ops::Add>::Output`
(expected type parameter,
    found associated type) [E0308]
<anon>:4     *x = *x + 1; 
                  ^~~~~~
error: aborting due to 2 previous errors

Let’s look at the Add trait’s definition:

pub trait Add<RHS = Self> {
    /// The resulting type after applying the `+` operator
    type Output;

    /// The method for the `+` operator
    fn add(self, rhs: RHS) -> Self::Output;
}

So Self + RHS produces an object of type <Self as Add<RHS>>::Output.

As you’re storing the value back in *x, the result of the calculation must be a T; thus we establish that the bound on T will need to be not Add but Add<???, Output = T>.

What, then, will ??? be? What is the type of 1? It’s not generic; it’s one of the ten known primitive integer types (isize, i8, i16, i32, i64, usize, u8, u16, u32, u64). This clearly won’t work, because the integral types don’t implement addition of lesser types—the default value for RHS of Self (that is, where T: Add means T: Add<Self>) is all you can count on, but 1 cannot be of type T.

The solution is to use a generic function that produces the value 1. There is one unstable in std::num::One, and a stable one in the num crate from crates.io, num::One. Using the former requires the Rust nightly, using the latter requires dropping the std::, adding an extern crate num; and adding num to your Cargo.toml dependencies section.

We also need a Copy bound to allow the *x of *x + 1 to work.

Here’s the final result:

#![feature(zero_one)]

use std::ops::Add;
use std::num::One;

fn inc<T: Copy + One + Add<T, Output = T>>(x: &mut T) {
    *x = *x + T::one(); 
}

fn main() {
    let mut x: i32 = 10;
    let mut y: u8 = 1;
    inc(&mut x);
    inc(&mut y);
    println!("{} {}", x, y);
}