A cell with interior mutability allowing arbitrary mutation actions

This is a very common pitfall for Rust beginners.

  1. Is it safe, i.e can some safe but malicious closure break Rust mutability/borrowing/lifetime rules by using this "universal" cell? I consider it safe since lifetime of the interior reference parameter prohibits its exposition beyond the closure call time. But I still have doubts (I'm new to Rust).

In a word, no.

Playground

fn main() {
    let mt_cell = MtCell::new(123i8);

    mt_cell.exec(|ref1: &mut i8| {
        mt_cell.exec(|ref2: &mut i8| {
            println!("Double mutable ref!: {:?} {:?}", ref1, ref2);
        })
    })
}

You're absolutely right that the reference cannot be used outside of the closure, but inside the closure, all bets are off! In fact, pretty much any operation (read or write) on the cell within the closure is undefined behavior (UB), and may cause corruption/crashes anywhere in your program.

  1. Maybe I'm re-inventing the wheel and there exist some templates or techniques solving the problem?

Using Cell is often not the best technique, but it's impossible to know what the best solution is without knowing more about the problem.

If you insist on Cell, there are safe ways to do this. The unstable (ie. beta) Cell::update() method is literally implemented with the following code (when T: Copy):

pub fn update<F>(&self, f: F) -> T
where
    F: FnOnce(T) -> T,
{
    let old = self.get();
    let new = f(old);
    self.set(new);
    new
}

Or you could use Cell::get_mut(), but I guess that defeats the whole purpose of Cell.

However, usually the best way to change only part of a Cell is by breaking it up into separate Cells. For example, instead of Cell<(i8, i8, i8)>, use (Cell<i8>, Cell<i8>, Cell<i8>).

Still, IMO, Cell is rarely the best solution. Interior mutability is a common design in C and many other languages, but it is somewhat more rare in Rust, at least via shared references and Cell, for a number of reasons (e.g. it's not Sync, and in general people don't expect interior mutability without &mut). Ask yourself why you are using Cell and if it is really impossible to reorganize your code to use normal &mut references.

IMO the bottom line is actually about safety: if no matter what you do, the compiler complains and it seems that you need to use unsafe, then I guarantee you that 99% of the time either:

  1. There's a safe (but possibly complex/unintuitive) way to do it, or
  2. It's actually undefined behavior (like in this case).

EDIT: Frxstrem's answer also has better info about when to use Cell/RefCell.


Your code is not safe, since you can call c.exec inside c.exec to get two mutable references to the cell contents, as demonstrated by this snippet containing only safe code:

let c: MyCell = MyCell::new(5);
c.exec(|n| {
    // need `RefCell` to access mutable reference from within `Fn` closure
    let n = RefCell::new(n);

    c.exec(|m| {
        let n = &mut *n.borrow_mut();
        
        // now `n` and `m` are mutable references to the same data, despite using
        // no unsafe code. this is BAD!
    })
})

In fact, this is exactly the reason why we have both Cell and RefCell:

  • Cell only allows you to get and set a value and does not allow you to get a mutable reference from an immutable one (thus avoiding the above issue), but it does not have any runtime cost.
  • RefCell allows you to get a mutable reference from an immutable one, but needs to perform checks at runtime to ensure that this is safe.

As far as I know, there's not really any safe way around this, so you need to make a choice in your code between no runtime cost but less flexibility, and more flexibility but with a small runtime cost.