Standard Cell struct provides interior mutability but allows only a few mutation methods such as set(), swap() and replace(). All of these methods change the whole content of the Cell. However, sometimes more specific manipulations are needed, for example, to change only a part of data contained in the Cell.
So I tried to implement some kind of universal Cell, allowing arbitrary data manipulation. The manipulation is represented by user-defined closure that accepts a single argument - &mut reference to the interior data of the Cell, so the user itself can deside what to do with the Cell interior. The code below demonstrates the idea:
use std::cell::UnsafeCell;
struct MtCell<Data>{
dcell: UnsafeCell<Data>,
}
impl<Data> MtCell<Data>{
fn new(d: Data) -> MtCell<Data> {
return MtCell{dcell: UnsafeCell::new(d)};
}
fn exec<F, RetType>(&self, func: F) -> RetType where
RetType: Copy,
F: Fn(&mut Data) -> RetType
{
let p = self.dcell.get();
let pd: &mut Data;
unsafe{ pd = &mut *p; }
return func(pd);
}
}
// test:
type MyCell = MtCell<usize>;
fn main(){
let c: MyCell = MyCell::new(5);
println!("initial state: {}", c.exec(|pd| {return *pd;}));
println!("state changed to {}", c.exec(|pd| {
*pd += 10; // modify the interior "in place"
return *pd;
}));
}
However, I have some concerns regarding the code.
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).
Maybe I'm re-inventing the wheel and there exist some templates or techniques solving the problem?
Note: I posted the question here (not on code review) as it seems more related to the language rather than code itself (which represents just a concept).
[EDIT] I'd want zero cost abstraction without possibility of runtime failures, so RefCell is not perfect solution.
This is a very common pitfall for Rust beginners.
In a word, no.
Playground
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.
Using
Cellis 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 (whenT: Copy):Or you could use
Cell::get_mut(), but I guess that defeats the whole purpose ofCell.However, usually the best way to change only part of a
Cellis by breaking it up into separateCells. For example, instead ofCell<(i8, i8, i8)>, use(Cell<i8>, Cell<i8>, Cell<i8>).Still, IMO,
Cellis 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 andCell, for a number of reasons (e.g. it's notSync, and in general people don't expect interior mutability without&mut). Ask yourself why you are usingCelland if it is really impossible to reorganize your code to use normal&mutreferences.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:EDIT: Frxstrem's answer also has better info about when to use
Cell/RefCell.