0

Is there a way to write this function without requiring AddAssign or Clone on T?

use std::ops::Add;

fn increment<T: Add<isize, Output = T>>(x: &mut T) {
    *x = *x + 1;
}

As written, I get the error:

error[E0507]: cannot move out of `*x` which is behind a mutable reference
 --> src/lib.rs:4:10
  |
4 |     *x = *x + 1;
  |          ^^ move occurs because `*x` has type `T`, which does not implement the `Copy` trait
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
dspyz
  • 5,280
  • 2
  • 25
  • 63
  • 1
    *without requiring* — What changes *are* we allowed to make? – Shepmaster Nov 16 '20 at 18:33
  • 2
    With the currently written function signature, this can only be solved via the solutions in [Temporarily move out of borrowed content](https://stackoverflow.com/q/29570781/155423); TL;DR "no, you can't do this without unsafe code". – Shepmaster Nov 16 '20 at 18:33
  • 2
    Since `add` takes ownership of the arguments, `x` would be left in an undefined state _if `add` panics_. That would introduce memory unsafety, and safe Rust does not permit that. See also [Cannot move out of borrowed content when trying to transfer ownership](https://stackoverflow.com/q/28258548/155423) – Shepmaster Nov 16 '20 at 18:35
  • 1
    There are plenty of other answers that involve changing the function signature / trait bounds, like adding `Copy` (but you've already ruled out `Clone` for unspecified reasons) or being able to add using a reference as the argument. It's not very fun for answerers to spend their time attempting to guess what _other_ limitations you will impose after they've spent their time providing an answer. – Shepmaster Nov 16 '20 at 18:40
  • @Shepmaster Your third comment makes it clear why this is impossible. I didn't consider the problem with recovering from panics. The comment I added and deleted was because I didn't realize you were the same person making three comments. I was trying to answer them one at a time. – dspyz Nov 16 '20 at 18:42

1 Answers1

0

Based on @Shepmaster's response in the comments, I gather that this is impossible without changing the function signature because the function being applied may panic leaving x in a memory-unsafe state if the panic is recovered from.

However by adding the Default constraint it becomes possible using mem::replace.

use std::mem;
use std::ops::Add;

fn increment<T: Default + Add<isize, Output = T>>(x: &mut T) {
    let y = mem::replace(x, Default::default());
    *x = y + 1;
}

Now if add panics, x will have the default T.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
dspyz
  • 5,280
  • 2
  • 25
  • 63
  • 2
    Not impossible, just not accepted in safe Rust. That's why crates like `replace_with` / `take_mut` exist. If you are going to use `Default`, you might as well do `*x = mem::take(x) + 1;` You could also change the bound to `for <'a> &'a mut T: Add` and do `*x = &mut *x + 1;` – Shepmaster Nov 16 '20 at 18:59
  • Hang on, Rust has `QuantifiedConstraints` ? I haven't seen `for` used in that way before. Where's the documentation on that? – dspyz Nov 16 '20 at 21:06
  • More information is linked in the duplicate [How to write a trait bound for adding two references of a generic type?](https://stackoverflow.com/q/34630695/155423) as well as [How does for<> syntax differ from a regular lifetime bound?](https://stackoverflow.com/q/35592750/155423). – Shepmaster Nov 16 '20 at 21:10