Search code examples
rustlifetimeborrow-checkermutability

How to dissociate mutability from lifetime in Rust?


I want to have two types (in my question-fitted examples I will mark them as IdHandler counting IDs, and IdUser using the IDs), where the dependent one of them should not outlive (hence, lifetimes) the dependency, and that's the only requirement. Figured the quickest solution would be using PhantomData<&'...>:

struct IdHandler {
    id_counter: u64,
}

struct IdUser<'a> {
    _a: PhantomData<&'a IdHandler>,
    id: u64,
}

Then, using a mutable reference with a specified lifetime, I made a function in impl IdHandler to create the users:

fn new_id_user<'a>(self: &'a mut IdHandler) -> IdUser<'a> {
    let res = IdUser {
        _a: PhantomData,
        id: self.id_counter,
    };

    self.id_counter += 1;

    res
}

Now, I will make a function that takes in a mutable reference and tries to make a pair of IdUsers:

fn f<'a>(id_handler: &'a mut IdHandler) -> (IdUser<'a>, IdUser<'a>) {
    let a1 = id_handler.new_id_user();
    let a2 = id_handler.new_id_user();
    (a1, a2)
}

And here's an example of how those types and functions may be used:

fn main() {
    let mut id_handler = IdHandler { id_counter: 0u64 };

    let (a1, a2) = f(&mut id_handler);

    assert_eq!(a1.id, 0u64);
    assert_eq!(a2.id, 1u64);
    assert_eq!(id_handler.id_counter, 2u64);

    id_handler.stop(); // fn stop(self) {}
    // Ideally here 'a is dead,
    // so you can't perform operations on IdUser<'a>

    // assert_eq!(a2.id, 1u64);
}

Given the fact that new_id_user(...) only uses the mutability for a moment, and then hopefully "releases" it back (as it is not used any longer than the function call - I do not know the exact rule Rust uses, but it works without _a/'a or when fn f(...) -> ()), I'd expect f(...) to work just fine. However, the compiler has greeted me with following:

error[E0499]: cannot borrow `*id_handler` as mutable more than once at a time
  --> src\main.rs:29:14
   |
27 | fn f<'a>(id_handler: &'a mut IdHandler) -> (IdUser<'a>, IdUser<'a>) {
   |      -- lifetime `'a` defined here
28 |     let a1 = id_handler.new_id_user();
   |              ------------------------ first mutable borrow occurs here
29 |     let a2 = id_handler.new_id_user();
   |              ^^^^^^^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here
30 |     (a1, a2)
   |     -------- returning this value requires that `*id_handler` is borrowed for `'a`

Does anyone know whether there is a solution that I'm missing, or am I doomed to allow IdUsers to outlive the IdHandler?


Solution

  • Unfortunately you cannot. Preserving a value with a lifetime will prevent the mutating the value from which it was derived even if you don't actually keep a reference to it. A common workaround if you want to ensure the lifetimes are bound while still being able to mutate is to use interior mutability (via Cell, RefCell, Mutex, etc).

    Here's a working example using Cell:

    use std::marker::PhantomData;
    use std::cell::Cell;
    
    struct IdHandler {
        id_counter: Cell<u64>,
    }
    
    impl IdHandler {
        fn new_id_user<'a>(self: &'a IdHandler) -> IdUser<'a> {
            let res = IdUser {
                _a: PhantomData,
                id: self.id_counter.get(),
            };
        
            self.id_counter.set(self.id_counter.get() + 1);
        
            res
        }
    }
    
    struct IdUser<'a> {
        _a: PhantomData<&'a IdHandler>,
        id: u64,
    }
    
    fn f<'a>(id_handler: &'a IdHandler) -> (IdUser<'a>, IdUser<'a>) {
        let a1 = id_handler.new_id_user();
        let a2 = id_handler.new_id_user();
        (a1, a2)
    }
    
    fn main() {
        let mut id_handler = IdHandler { id_counter: Cell::new(0u64) };
    
        let (a1, a2) = f(&mut id_handler);
    
        assert_eq!(a1.id, 0u64);
        assert_eq!(a2.id, 1u64);
    }