Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust generic AddAssign with references

Tags:

rust

traits

A while ago I looked into writing a generic iterator for the Fibonacci sequence that could accept both primitive numbers as well as custom types (such as bignums). After failing to get a version working for both the primitive types and bignums, I stumbled upon this question:

How to write a trait bound for adding two references of a generic type?

Which used so called Higher Ranked Trait Bounds to solve the problem with this particular issue.

Now however, I'm trying to use a similar strategy to use the *_assign operators instead. In particular, I'm trying to get something similar to this working:

use std::ops::{Add, AddAssign};

fn add_test<'a, T>(x: &'a T, y: &'a T) -> T
where
    for<'b> &'b T: Add<Output = T>,
{
    x + y
}

fn add_assign_test<'a, T>(x: &'a mut T, y: &'a T) -> T
where
    for<'b> &'b mut T: AddAssign<&'b T>,
    T: Clone,
{
    x += y;
    x.clone()
}

fn main() {
    println!("add_test()={}", add_test(&1, &2));
    println!("add_assign_test()={}", add_assign_test(&mut 2, &2));
}

add_test() works as expected but I'm unable to get add_assign_test() to work in a similar way. The errors I'm getting suggest that there might not actually exist an implementation for this kind of behaviour on the primitive types:

error[E0277]: the trait bound `for<'b> &'b mut _: std::ops::AddAssign<&'b _>` is not satisfied
  --> src/main.rs:21:38
   |
21 |     println!("add_assign_test()={}", add_assign_test(&mut 2, &2));
   |                                      ^^^^^^^^^^^^^^^ no implementation for `&'b mut _ += &'b _`
   |
   = help: the trait `for<'b> std::ops::AddAssign<&'b _>` is not implemented for `&'b mut _`
   = note: required by `add_assign_test`

I could create a macro that creates implementations for these operators that actually takes references to the primitive types, but that seems a little wasteful. Is there any other way to achieve the same effect?

like image 726
Xaldew Avatar asked Aug 11 '17 08:08

Xaldew


2 Answers

Just a tiny oversight in your code. Let's look at the trait:

pub trait AddAssign<Rhs = Self> {
    fn add_assign(&mut self, rhs: Rhs);
}

The receiver of the method is already &mut self and not self. The reason that you had to do the extra work with Add is because it accepts self as receiver. For AddAssign this means: if a type T implements AddAssign, you can call the method add_assign() on a &mut T!

Thus, instead of writing:

where for <'b> &'b mut T: AddAssign<&'b T>,

... you would write:

where for <'b> T: AddAssign<&'b T>,

(No other line changed so far)


However, you notice that the code still won't compile:

error[E0277]: the trait bound `for<'b> {integer}: std::ops::AddAssign<&'b {integer}>` is not satisfied
  --> src/main.rs:13:38
   |
13 |     println!("add_assign_test()={}", add_assign_test(&mut 2, &2));
   |                                      ^^^^^^^^^^^^^^^ no implementation for `{integer} += &'b {integer}`
   |
   = help: the trait `for<'b> std::ops::AddAssign<&'b {integer}>` is not implemented for `{integer}`
   = note: required by `add_assign_test`

The reason is simple: there is simply no implementation of AddAssign for primitive types which takes an immutable reference as rhs (Docs). I don't know if this is an oversight -- it could be worth opening an issue on the Rust repo.

To verify the above code works, I write my own type and implemented AddAssign appropriately: Playground.

like image 184
Lukas Kalbertodt Avatar answered Sep 22 '22 01:09

Lukas Kalbertodt


Corrected the code snippet based on Lukas' reply:

use std::ops::{Add, AddAssign};

fn add_test<'a, T>(x: &'a T, y: &'a T) -> T
where
    for<'b> &'b T: Add<Output = T>,
{
    x + y
}

fn add_assign_test<'a, T>(x: &'a mut T, y: &'a T) -> T
where
    for<'b> T: AddAssign<&'b T>,
    T: Clone,
{
    *x += y;
    x.clone()
}

fn main() {
    println!("add_test()={}", add_test(&1, &2));
    println!("add_assign_test()={}", add_assign_test(&mut 2, &2));
}

It appears that this is an issue in Rust itself. There is currently a tracker for this issue.

Until this is fixed, there's two possible workarounds:

  1. Create named-tuples for each primitives and implement OpAssign for those types instead. This does however force you to 'cast' all primitives to your custom type.

  2. Duplicate the 'generic' code with specializations for the primitives.

like image 27
Shepmaster Avatar answered Sep 20 '22 01:09

Shepmaster