Rust, std::cell::Cell - get immutable reference to inner data
Looking through the documentation for std::cell::Cell, I don't see anywhere how I can retrieve a non-mutable reference to inner data. There is only the get_mut
method: https://doc.rust-lang.org/std/cell/struct.Cell.html#method.get_mut
I don't want to use this function because I want to have &self
instead of &self mut
.
I found an alternative solution of taking the raw pointer:
use std::cell::Cell;
struct DbObject {
key: Cell<String>,
data: String
}
impl DbObject {
pub fn new(data: String) -> Self {
Self {
key: Cell::new("some_uuid".into()),
data,
}
}
pub fn assert_key(&self) -> &str {
// setup key in the future if is empty...
let key = self.key.as_ptr();
unsafe {
let inner = key.as_ref().unwrap();
return inner;
}
}
}
fn main() {
let obj = DbObject::new("some data...".into());
let key = obj.assert_key();
println!("Key: {}", key);
}
Is there any way to do this without using unsafe? If not, perhaps RefCell will be more practical here?
Thank you for help!
Solution 1:
First of, if you have a &mut T
, you can trivially get a &T
out of it. So you can use get_mut
to get &T
.
But to get a &mut T
from a Cell<T>
you need that cell to be mutable, as get_mut
takes a &mut self
parameter. And this is by design the only way to get a reference to the inner object of a cell.
By requiring the use of a &mut self
method to get a reference out of a cell, you make it possible to check for exclusive access at compile time with the borrow checker. Remember that a cell enables interior mutability, and has a method set(&self, val: T)
, that is, a method that can modify the value of a non-mut
binding! If there was a get(&self) -> &T
method, the borrow checker could not ensure that you do not hold a reference to the inner object while setting the object, which would not be safe.
TL;DR: By design, you can't get a &T
out of a non-mut
Cell<T>
. Use get_mut
(which requires a mut
cell), or set
/replace
(which work on a non-mut
cell). If this is not acceptable, then consider using RefCell
, which can get you a &T
out of a non-mut instance, at some runtime cost.
Solution 2:
In addition to to @mcarton answer, in order to keep interior mutability sound, that is, disallow mutable reference to coexist with other references, we have three different ways:
- Using
unsafe
with the possibility of Undefined Behavior. This is whatUnsafeCell
does. - Have some runtime checks, involving runtime overhead. This is the approach
RefCell
,RwLock
andMutex
use. - Restrict the operations that can be done with the abstraction. This is what
Cell
,Atomic*
and (the unstable)OnceCell
(and thusLazy
that uses it) does (note that the thread-safe types also have runtime overhead because they need to provide some sort of locking). Each provides a different set of allowed operations:-
Cell
andAtomic*
do not let you to get a reference to the contained value, and only replace it as whole (basically,get()
andset
, though convenience methods are provided on top of these, such asswap()
). Projection (cell-of-slice to slice-of-cells) is also available forCell
(field projection is possible, but not provided as part of std). -
OnceCell
allows you to assign only once and only then take shared reference, guaranteeing that when you assign you have no references and while you have shared references you cannot assign anymore.
-
Thus, when you need to be able to take a reference into the content, you cannot choose Cell
as it was not designed for that - the obvious choice is RefCell
, indeed.