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?
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.
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:
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.
Duplicate the 'generic' code with specializations for the primitives.
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