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:

  1. Using unsafe with the possibility of Undefined Behavior. This is what UnsafeCell does.
  2. Have some runtime checks, involving runtime overhead. This is the approach RefCell, RwLock and Mutex use.
  3. Restrict the operations that can be done with the abstraction. This is what Cell, Atomic* and (the unstable) OnceCell (and thus Lazy 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 and Atomic* do not let you to get a reference to the contained value, and only replace it as whole (basically, get() and set, though convenience methods are provided on top of these, such as swap()). Projection (cell-of-slice to slice-of-cells) is also available for Cell (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.