dspyz
dspyz

Reputation: 5524

How to update a mutable reference to a noncopyable type?

I want to do something like

impl Widget {
    fn foo(self, rhs: Widget) -> Self {
        // Some stuff
    }

    fn update(&mut self, rhs: Widget) {
        *self = (*self).foo(rhs)
    }
}

but the compiler complains "cannot move out of borrowed context." What's the right way to do this?

Upvotes: 1

Views: 87

Answers (2)

malbarbo
malbarbo

Reputation: 11187

One option is to use the take_mut crate, which offers the take functions that do exactly what you want:

take allows for taking T out of a &mut T, doing anything with it including consuming it, and producing another T to put back in the &mut T.

As pointed by Francis Gagné, the problem of doing this is that the &mut reference will be in a invalid state if a panic happens, and this can lead to undefined behavior. The take_mut approach is:

During take, if a panic occurs, the entire process will be exited, as there's no valid T to put back into the &mut T.

Here is the code using take:

extern crate take_mut;

struct Widget;

impl Widget {
    fn foo(self, rhs: Widget) -> Self {
        self
    }

    fn update(&mut self, rhs: Widget) {
        take_mut::take(self, |s| s.foo(rhs));
    }
}

Upvotes: 1

Francis Gagné
Francis Gagné

Reputation: 65907

Do it the other way around:

impl Widget {
    fn op(mut self, rhs: Widget) -> Self {
        self.update(rhs);
        self
    }

    fn update(&mut self, rhs: Widget) {
        // Some stuff
    }
}

You cannot move out of a borrowed pointer, because moving makes the source unusable, but since you don't own the source, that information would have to propagate back to the owner, and Rust doesn't support that.

You might say "but I'm assigning a new value to *self before the function returns!". The problem with that is that if there's a panic between the move and the assignment, *self would still be left with no valid value. This is especially problematic if dropping self is not a no-op (though Rust doesn't care if dropping is a no-op or not here).

Upvotes: 3

Related Questions