3

I'm working with two separate functions.

  • The first one takes an owned instance of a structure then returns it.
  • The second one takes a mutable reference but needs to use the first function.
// This structure is not `Clone`.
struct MyStruct;

fn take_owned(s: MyStruct) -> MyStruct {
    // Do things
    s
}

fn take_mut(s: &mut MyStruct) {
    *s = take_owned(s /* problem */);
}

I thought about a solution but I'm not sure it's sound:

use std::ptr;

// Temporarily turns a mutable reference into an owned value.
fn mut_to_owned<F>(val: &mut MyStruct, f: F)
where
    F: FnOnce(MyStruct) -> MyStruct,
{
    // We're the only one able to access the data referenced by `val`.
    // This operation simply takes ownership of the value.
    let owned = unsafe { ptr::read(val) };

    // Do things to the owned value.
    let result = f(owned);

    // Give the ownership of the value back to its original owner.
    // From its point of view, nothing happened to the value because we have
    // an exclusive reference.
    unsafe { ptr::write(val, result) };
}

Using this function, I can do that :

fn take_mut(s: &mut MyStruct) {
    mut_to_owned(s, take_owned);
}

Is this code sound? If not, is there a way to safely do this?

Gymore
  • 585
  • 2
  • 10
  • 1
    The main problem AFAIK is the fact that `take_owned` can panic, and in this case ownership will never be returned, whatever you're doing inside the caller. – Cerberus Mar 12 '21 at 10:02
  • Ah, yes. Would using something like [`std::panic::catch_unwind`](https://doc.rust-lang.org/std/panic/fn.catch_unwind.html) solve the problem, then? – Gymore Mar 12 '21 at 10:28

1 Answers1

5

Someone has already implemented what you're looking for, in the crate named take_mut.

Function take_mut::take

pub fn take<T, F>(mut_ref: &mut T, closure: F) 
where
    F: FnOnce(T) -> T, 

Allows use of a value pointed to by &mut T as though it was owned, as long as a T is made available afterwards.

...

Will abort the program if the closure panics.

Function take_mut::take_or_recover

pub fn take_or_recover<T, F, R>(mut_ref: &mut T, recover: R, closure: F) 
where
    F: FnOnce(T) -> T,
    R: FnOnce() -> T, 

Allows use of a value pointed to by &mut T as though it was owned, as long as a T is made available afterwards.

...

Will replace &mut T with recover if the closure panics, then continues the panic.

The implementation is essentially what you have, plus panic handling as described.

Kevin Reid
  • 37,492
  • 13
  • 80
  • 108