Restricting the usage of a mutable reference

Hi,

I often run into the same problem: I want to pass around a mutable reference, but I also want to restrict the usage (e.g. because there is some invariant). My preferred solution is to create a wrapper type (very similar to std::fmt::Formatter). For example:

// Cannot implement `Copy` or `Clone` because of `&'a mut u64`
pub struct IdGen<'a> {
    // Only incrementing is allowed
    state: &'a mut u64,    
}

impl<'a> IdGen<'a> {
    pub fn new(state: &'a mut u64) -> Self {
        Self { state }
    }
    
    pub fn next_id(&mut self) -> u64 {
        let id = self.state.checked_add(1).unwrap();
        *self.state = id;
        id
    }
}

pub trait ValueGen {
    // I don't trust the implementors, so I use `IdGen` instead of `&mut u64`
    fn gen(id_gen: IdGen) -> Self;
}

pub struct Foo { id: u64 }
impl ValueGen for Foo {
    fn gen(mut id_gen: IdGen) -> Self {
        Foo { id: id_gen.next_id() }
    }
}

This works, but it's very awkward to use because IdGen cannot be Copy or Clone:

fn main() {
    let mut state = 0;
    let id_gen = IdGen::new(&mut state);
    
    let foo1 = Foo::gen(id_gen);
    //let foo2 = Foo::gen(id_gen); // Does not compile because not `Copy`
}

The only solution I found so far is implementing an Clone-like function:

impl<'a> IdGen<'a> {
    // Note: It does not return `Self`.
    // It took me hours to realize why `Self` would not work...
    pub fn copy(&mut self) -> IdGen {
        IdGen { state: self.state }
    }
}

fn main() {
    let mut state = 0;
    let mut id_gen = IdGen::new(&mut state);
    
    let foo1 = Foo::gen(id_gen.copy());
    let foo2 = Foo::gen(id_gen); // Its works!
}

This is better than "does not compile", but it's verbose.

So, questions:

  • Is there a better solution for the initial problem (restricting the usage of a mutable reference)?
  • If not, is there a better solution for the non-Copy-problem?

PS:

A solution that I tried but could not uphold the invariant was to take &mut IdGen instead of IdGen:

pub trait BadValueGen {
    // `&mut IdGen` instead of `IdGen`
    fn gen(id_gen: &mut IdGen) -> Self;
}

pub struct BadFoo { id: u64 }
impl BadValueGen for BadFoo {
    fn gen(id_gen: &mut IdGen) -> Self {
        let value = BadFoo { id: id_gen.next_id() };
    
        // Burn invariant! Burn!
        let counter = Box::leak(Box::new(0));
        *id_gen = IdGen::new(counter);
    
        value
    }
}

Playground link

Edit: Formatting

Posted Feb 3, 2021, 11:54 AM UTC · edited

612
Loading comments…