Applying f: Fn(T, K) -> T to &mut T without clone

We have:

x: &mut T;
f: Fn(T, K) -> T;
k: K

can we do a x = f(x, k) without the use of a clone ?

The reason I believe why this could be done is that the &mut ref implies nothing else has access to it. So it seems there is some way to "grab the object, apply the function, and put it back"

If T: Default, or you can otherwise provide a sensible empty/sentinel value to temporarily fill in the place, you can use mem::take() or mem::swap() to move the value out and then assign it back in.

If there's no such sensible sentinel value, then I believe it is not possible to implement this soundly. You could technically use ptr::read() and ptr::write(), although I would not recommend doing this, because I don't see how it is at all possible to get it right in the presence of panicking, at least not as-is.

The problem with the usual "hole" approach (a temporary RAII guard that puts a valid value into the reference when a panic occurs) is that the original value will have already been consumed by the time the panic occurs, since it is passed to the function by value. So there's nothing to put back in that case. You could instead try asserting that the function does not panic, although there isn't a 100% reliable way for achieving that.

The safe and the unsound approach (along with the double free) described above are demonstrated in this Playground.

1 Like

&mut is invariant, so unfortunately you cannot use a simple T to match it. & can be matched with T, while &mut can only be matched against a &mut T.

1 Like

You can do this by using std::ptr::read to get the value, mutate it, and then std::ptr::write to put the modified value back into the original location.

The tricky part is what happens if f panics between the read and the write. Crates like take_mut handle this by aborting the program on panic, or requiring the caller to provide a replacement value on panic.