arvau

Arvau Documentation / Turning &mut into owned (temporarily)

Prerequisite knowledge:

  • Rust basics

  • std::mem::swap

  • std::default::Default

  • Rust 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.