Move out of borrowed content and assignment


#1

Hi,

I stumbled on this “shouldn’t this be not a problem” problem.

When something is borrowed the contents of cannot be moved. That does make sense for me. But I don’t see a problem when the same function call that moves the value assigns the value afterwards.

  1. Can someone explain why this is a problem? Or is it just something that is not considered worth an exception of this “cannot move out of borrowed content” rule?
  2. Can someone suggest a functional-style workaround here?

struct Inner;

struct Outer {
    inner: Inner
}

// This does work but is not very functional
fn do_outer_works(outer: &mut Outer) {
    fn do_inner(inner: &mut Inner) {
        // mutate inner
    }

    do_inner(&mut outer.inner);
}

// This does not work
fn do_outer_works_not(outer: &mut Outer) {
    fn do_inner(inner: Inner) -> Inner {
        Inner
    }

    outer.inner = do_inner(outer.inner);
//                         ^^^^^ cannot move out of borrowed content
}

fn main() {
    let mut outer = Outer{ inner: Inner };

    do_outer_works(&mut outer);
    do_outer_works_not(&mut outer);
}

#2

If it panicked while the value was moved, then the unwinder would get out to a point where it needs to drop the owned outer and all its fields, but that code wouldn’t know that inner was moved.

Such questions about panic handling were the main controversy in RFC 1736.


#3

As for a workaround, you can wrap the value in an Option so that take() can move it out. But this does mean that you have to deal with the possibility of None everywhere.


#4

Ouh, didn’t thought about that. Thanks for the quick answer!


#5

You can also use std::mem::replace directly (that’s what Option::take uses internally). To keep it “functional”, just don’t try to move out the inner value while you’re computing the replacement value - borrow it temporarily, and then mem::replace with the new value.

Why do you like this approach more than using mutable references? Rust’s borrow guarantees come in handy here, and I’m not sure the “functional” form buys you much?


#6

Borrowing the “Inner” while computing a new one is a option but I’ve got big values there which I would like to move instead of copying.
I’ve got a state machine that computes a new “Inner” and that is really clean and clear in a functional style. (At least I think so).


#7

Yeah this issue comes up a lot in state machine code (e.g. Futures). I too find the functional form more readable. I usually end up using the mem::replace trick.

There’s also a package take_mut that gives you what you want at the cost of aborting the program if any panics occur.


#8

State machine is a good example, yeah - agreed :thumbsup: