Arvau Documentation / Turning &mut into owned (temporarily)
Arvau Documentation / Turning &mut into owned (temporarily)
Prerequisite knowledge:
Rust basics
std::mem::swapstd::default::DefaultRust macro_rules! basics
Often in Rust I find myself with a mutable reference to something I need to own for another method.
Take this code:
mod black_box {
#[derive(Default)]
struct BlackBox {
// not important
}
impl BlackBox {
pub fn to_bool(self) -> (Self, bool) {
// not important
}
}
}
use black_box::*;
struct MyStruct {
field: BlackBox
}
impl MyStruct {
pub fn my_to_bool(&mut self) -> bool {
todo!()
}
}
impl Default for MyStruct {
fn default() -> Self {
Self {field: BlackBox::default()}
}
}
Because the only way to access BlackBox::to_bool() is with an owned BlackBox, the normal way to implement my_to_bool is like this:
impl MyStruct {
pub fn my_to_bool(&mut self) -> bool {
let mut swapped = Self::default(); // create an instance of Self that will never be used
std::mem::swap(self, &mut swapped); // move self to swapped
let (mut new, out) = swapped.field.to_bool(); // run the function and assign to swapped
std::mem::swap(self, &mut new); // move swapped back into self
return out;
}
}
This is not very readable and requires that you remember to swap back once finished. Instead, you can use a simple macro:
macro_rules! mut_ref_to_owned {
($ident:ident => $block:expr) => {
let mut s = Default::default();
std::mem::swap($ident, &mut s);
let out;
{
#[allow(unused_mut)]
let mut $ident = s;
out = {$block};
s = $ident;
}
std::mem::swap($ident, &mut s);
out
};
}
impl MyStruct {
pub fn my_to_bool(&mut self) -> bool {
let s = self; // since the macro does not work with the self keyword
mut_ref_to_owned(s => { // s becomes an owned variable here, as if the function definition was `my_to_bool(s: Self)`
let out;
(s, out) = s.field.to_bool();
out
})
}
}
It is a zero-cost abstraction over the code you would write anyway, but is much safer and easier to read. It only works for types implementing Default::default() though.