Moving out of a borrow (exception)?


#1

A few times now I have wanted to apply a function like this:

fn succ<T>(x : self) -> Self

fn test<T>(x : &mut T) {
    *x = x.succ()
}

This generates an error about moving out of a borrow, but it is actually safe because we move the modified value back into x. It is possible to rewrite this:

fn inc<T>(&mut self);
fn succ<T>(mut self) -> Self {
    self.inc();
    self
}

fn test<T>(x : &mut T) {
    x.inc()
}

Which seems functionally equivalent to me, except it requires an additional function which clutters the API.

Could the borrow checker be changed to allow this pattern?

Edit: thinking more about this, I can see why it is not allowed, because ‘succ’ could panic after moving ‘x’, but it also seems that it should somehow be possible to to apply the ‘succ’ function as a mutation of x.


#2

You need to consider exception safety. What happens if succ panics?

use std::mem;

struct Foo {
    s: String
}

impl Drop for Foo {
    fn drop(&mut self) {
        println!("I can see `{}`", self.s);
    }
}

fn boo(s: String) -> String {
    drop(s);
    panic!();
}

/* What would the destructor print if this compiled?
fn foo(s: &mut String) {
    *s = boo(*s);
}
*/

fn bar(s: &mut String) {
    // this is known as "pre-poop your pants"
    let t = mem::replace(s, "Dummy".to_string());
    mem::replace(s, boo(t));
}

fn main() {
    let mut f = Foo { s: "Hello!".to_string() };
    bar(&mut f.s)
}

#3

Yes, I realised that after posting, but I still think if I can know that:

x = x + 1

is a mutation of x, why can’t the compiler figure it out, and perform an AST rewrite as necessary?

For example the above can be trivially rewritten to:

x += 1

And this is what we have with “succ” and “inc”, the same relationship as between the operators “+” and “+=”.


#4

i32 is Copy. If you add T: Copy the borrow checker won’t complain I believe.


#5

True I could also write:

x = x.clone().succ();

But it is less efficient because of the copy, which isn’t necessary, it the compiler could recognise the pattern and replace with the appropriate alternative. (see + vs += above).


#6

If the type has a cheap default, you could swap it out and then write back the result. That’s basically what @gkoz did with the "Dummy" value.

let y = ::std::mem::replace(x, T::default());
*x = y.succ();

There’s still some shallow copying as you move values around, but I think that’s the best you can hope for without explicitly modifying in-place (like your inc).


#7

It even works on one line, if you don’t like the local variable:

*x = ::std::mem::replace(x, T::default()).succ();

#8

Actually, you could fix this with an aborting drop guard. One could provide some:

extern crate libc;
use std::{ptr, mem};
fn transform_or_abort<T>(value: &mut T, transform: FnOnce(T) -> T) {
    struct DropGuard;
    impl Drop for DropGuard {
        fn drop(&mut self) {
            libc::abort();
        }
    }
    let _drop_guard = DropGuard;
    let tmp = ptr::read(value as *const T);
    ptr::write(value as *mut T, (transform)(tmp));
    mem::forget(_drop_guard);
}

That aborts if transform panics.


#9

I think there’s a crate that provides an unsafe function which can apply an fn(T) -> T to an &mut T (it moves the T out of the borrow, leaving the value under the reference temporarily unitialized). Its unsafe because its your responsibility to guarantee the function you pass won’t panic.

I can’t remember what its called though. Does anyone remember what I’m talking about?


#10

I found replace_map, but that’s out of date with its use of ptr::read_and_zero.

I would have written it like this:

pub unsafe fn replace_map<T, F>(src: &mut T, f: F)
where F: FnOnce(T) -> T {
    ptr::write(src, f(ptr::read(src)));
}

#11

The answer is probably to give up writing “x = x + 1” and get used to mutating the data through a reference as in “x += 1”, and avoid unsafe solutions, although it seems that a more sophisticated compiler might be able to rewrite one into the other.


#12

As it makes sense to have both versions, because one is necessary for borrowed collections, and the other where you have a temporary, so you can use like this

x.half_nonnegative_mut()

let y = x.clone().half_nonnegative()

What would the typical way to differentiate between the functions in Rust:

fn half_nonnegative_mut(&mut self) {
    *self >>= Self::one()
}

fn half_nonnegative(mut self) -> Self {
    self.half_nonnegative_mut();
    self
}

I am not sure “_mut” makes sense as an ending? What would you call these two functions?

Edit: I have checked the guide, and “_mut” probably is the correct ending if I think the owning version will be the “normal” one.


#13

Doesn’t a _mut suffix usually distinguish cases where the output may or may not be mutable? I’m thinking like slice::chunks vs. slice::chunks_mut, where they otherwise behave the same.

I think the into_ prefix is more common to take ownership and return something else.

If I just saw half_nonnegative, my intuition would be that this is something taking &self and returning a new value. I’m not sure what to call the in-place modification though. Perhaps it needs an active verb – halve_nonnegative?


#14

Could you maybe make it a trait method and implement provide a blanket impl?

Something like (untested):

trait HalfNonnegative {
    fn half_nonnegative(mut self) -> Self;
}

impl<T: HalfNonnegative> HalfNonnegative for &mut T {
    fn half_nonnegative(mut self) -> Self {
        (*self).half_nonnegative_mut();
        self
    }
}