Cannot get a mutable reference from LazyCell

(Rust 1.80.0)

use std::cell::LazyCell;

fn main() {
    let mut buf = LazyCell::new(|| [0u8; 4096]);
    use_buffer(&mut *buf);
}

fn use_buffer(_buf: &mut [u8; 4096]) {}
error[E0596]: cannot borrow data in dereference of `LazyCell<[u8; 4096], {closure@src/main.rs:4:33: 4:35}>` as mutable
 --> src/main.rs:5:16
  |
5 |     use_buffer(&mut *buf);
  |                ^^^^^^^^^ cannot borrow as mutable
  |
  = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `LazyCell<[u8; 4096], {closure@src/main.rs:4:33: 4:35}>`

LazyCell doesn't seem to have an API to get a mutable reference to the inner value.

Is it intentional that DerefMut and other APIs to get a mutable reference are not implemented? Or are they not implemented now, but could be in the future?

LazyCell is designed to be initialized once and not modified afterwards. It's basically a OnceCell where the function you use to initialize the cell is the same, everywhere you'd call OnceCell::get_or_init:

You can use OnceCell instead of LazyCell if you need mutable access to the inner value, as OnceCell provides a get_mut method.

1 Like

Does this mean that the API to get &mut T from LazyCell will not be implemented in the future?

What I don't like so much about OnceCell is that when I call get_mut, I need to call unwrap because it returns Option, and when I call get_mut_or_init, I need to pass an initializer function each time. I was hoping that LazyCell might solve this problem.

I think the implementation details of LazyCell::force make it impossible for it to provide a get_mut method, as calling this method (implicitly done by Deref::deref as well) invalidates any existing mutable references to the cell's contents. Because LazyCell does not have any runtime checks like RefCell, it can't safely guarantee that no mutable references exist when force is called. To solve this, RefCell never provides mutable access to its contents in the first place.

Sounds to me like you could solve this with a type wrapping a OnceCell and an initializer function?

2 Likes

What about interior mutability? Does LazyCell<RefCell> make sense?

Assuming a fn get_mut(&mut self) -> &mut T, you wouldn't be able to call force or deref while you hold that mutable reference because the cell would also be mutably borrowed.

Cell also has no runtime checks like RefCell, but Cell::get_mut exists and has no safety problem despite Cell::set invalidating any reference to the Cell's content.

1 Like

Oh, right. I wonder why LazyCell does not have a get_mut then. This should do it without the use of any unsafe:

pub fn get_mut(&mut self) -> &mut T {
    let state = self.state.get_mut();
    match state {
        State::Init(data) => data,
        State::Uninit(_) => {
            let State::Uninit(f) = mem::replace(state, State::Poisoned) else {
                unreachable!()
            };

            let data = f();

            *state = State::Init(data);
            let State::Init(data) = state else {
                unreachable!()
            };
            data
        }
        State::Poisoned => panic!("LazyCell has previously been poisoned"),
    }
}

Playground.

(I just took the source of force and really_init and modified it to use UnsafeCell::get_mut instead of UnsafeCell::get)

It's probably worth mentioning that both once_cell::unsync::Lazy and once_cell::sync::Lazy have a get_mut() method that returns Option<&mut T>. As far as I understand, they are the ancestors of sorts of the standard library LazyCell and LazyLock, respectively.

2 Likes

If you need a mutable lazy cell, you need Mutex<Option<T>>, or possibly ArcSwap.

Everything that once/lazy cell does differently than a Mutex is thanks to immutability. If you try to add mutability to it, it becomes a paradox like trying to add negative numbers to unsigned integers.

Note that mutex will give you mutability via a MutexGuard that gives exclusive &mut only temporarily. Nothing in Rust [1] will ever give you an unrestricted global &mut, since you logically can't have references that are guaranteed exclusive and globally shared at the same time.


  1. except horribly unsafe footgun static mut — don't even think about using it ↩ī¸Ž

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.