1

I have a mutable reference to a structure and I want to change one of its fields by passing it through a function and set the result as the new value of the field. However, I get a "cannot move out of borrowed content" compile error.

This is a minimalistic example that demonstrates my issue:

struct InnerStruct {
    num: usize,
}

struct MyStruct {
    inner_struct: InnerStruct,
}

fn do_something(inner_struct: InnerStruct) -> InnerStruct {
    inner_struct
}

fn main() {
    let mut my_struct = MyStruct {
        inner_struct: InnerStruct { num: 0 },
    };

    let my_struct_ref = &mut my_struct;
    // This line compiles:
    // my_struct_ref.inner_struct = InnerStruct { num: 0 };
    my_struct_ref.inner_struct = do_something(my_struct_ref.inner_struct);
}

This is the compilation error that I get:

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:21:47
   |
21 |     my_struct_ref.inner_struct = do_something(my_struct_ref.inner_struct);
   |                                               ^^^^^^^^^^^^^ cannot move out of borrowed content

The function do_something has to take ownership over inner structure to perform its job (In the real source it operates on Tokio futures).

I've many posts with the same title "cannot move out of borrowed content", and the solution to all of them was to clone something or pass a reference instead of move ownership, but I can not apply these solutions to my case.

I can't understand what the Rust compiler is trying to defend against in this case. In every possibility that I can think of my_struct is kept consistent.

Having this line instead compiles successfully:

my_struct_ref.inner_struct = InnerStruct { num: 0 };

The following three lines also work:

let inner_struct2 = InnerStruct { num: 0 };
let inner_struct = std::mem::replace(&mut my_struct_ref.inner_struct, inner_struct2);
my_struct_ref.inner_struct = do_something(inner_struct);

How come this is considered safe, while the first code isn't?

I will appreciate any ideas about how to solve this, or explanations about what is wrong/unsafe with what I'm trying to do.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
real
  • 639
  • 1
  • 6
  • 15

1 Answers1

3

If do_something panics unwinding is impossible - you cannot restore my_struct_ref to a valid state, but the outer context requires my_struct_ref to be valid.

It would be nice if there was a generic way of saying "if this panics just crash instead of unwind", which would make such operation safe as far as I can tell.

take from the take_mut crate provides a safe wrapper (it catches an unwinding panic and makes it abort instead, solving the problem with unwinding).

Stefan
  • 5,304
  • 2
  • 25
  • 44
  • I thought that a panic is a sort of crashing. Is it possible to recover after a panic ? – real Nov 26 '17 at 14:06
  • 1
    I think the docs for [`catch_unwind`](https://doc.rust-lang.org/stable/std/panic/fn.catch_unwind.html) explain it very well - "This function only catches unwinding panics, not those that abort the process." – Stefan Nov 26 '17 at 15:13