As_slice_of_cells() for OnceCell


OnceCell has just been stabilized in Rust 1.70. However, OnceCell lacks an equivalent to the convenience function as_slice_of_cells() that Cell has. Assuming that OnceCell also has the same memory layout as Cell, it would be trivial to implement this for cell (Cell just directly casts itself into the slice's elements, since Cell<T> and T have the same memory layout). Correction: OnceCell<T> has the same layout as Option<T> not T.


OnceCell has less overhead than Cell, especially when dealing with structures that can contain multiple of these. Less copy means less memory usage. Also, slices are the best way to implement features for contiguous types using AsMut<[T]> and AsRef<[T]> traits.

Unless I'm missing something, this is impossible. OnceCell<T> is internally an UnsafeCell<Option<T>>, so it doesn't have the same memory layout as Cell, which means it doesn't have the same layout as T (excluding types which have an inline representation for None, but I don't believe you can use that).

Genuine question: does it? OnceCell is larger in memory than a Cell. get looks the same for both, and set is more complicated for OnceCell than for Cell.


It doesn't. It has the same layout as Cell<Option<T>> though (ignoring the missing #[repr(transparent)]. (Assuming you aren't talking about OnceLock, the thread-safe version.)

In any case, you should probably make a point of explaining the intended use-case here, as that's not immediately clear to me. (Maybe some small code example.)


A small example with an implementation of bubble sort algorithm, using Cell<T> for interior mutability.

pub fn bubble_sort<Item>(v: &mut [Item])
    Item: Ord + Copy + Clone + Debug, 
    let v: &[Cell<Item>] = Cell::from_mut(v).as_slice_of_cells(); 
    for i in 0..v.len() {
        for w in v[..v.len() - i].windows(2) {
            if &w[0].get() > &w[1].get() {
                Cell::swap(&w[0], &w[1])

Here, the get() method copies the underlying data, which is trivial for integers. However in cases of types with a bigger memory layout, the extra memory overhead can induce performance overhead. Methods for taking a once_cell of a slice and turn it into a slice of once_cell would be convenient while still respecting the invariants of OnceCell.

Digression: A swap method for OnceCell would be nice too, since the implementation for Cell::swap() is unsafe under the hood, and wrapping these methods in a safe call is convenient and sound.

I'm not sure what you want to show with the bubble_sort example, but OnceCell doesn't and can't have the properties you need to replace Cell there:

  • it does not have a from_mut method because is not #[repr(transparent)] and cannot be since it stores an Option<T>, which has a different layout than T. Even if you replace the Option with something else, the layout will surely be different than T, because it needs some state to know whether the inner item has been initialized or not.

  • for a similar reason as the previous point, OnceCell cannot (at least not without black magic) implement as_slice_of_cells, because it has a single flag (the Option discriminant) that indicates whether it has been initialized or not. Implementing as_slice_of_cells would imply it has as many flags as elements (because you could initialize each one independently), which is not the case.

  • it does not have a swap method because it cannot modify the inner item after it has been initialized. Being able to handle out shared references (which Cell can't do, as you consider inconvenient) is possible thanks to this limitation.

OnceCell would have to implement swap with unsafe too, and that would not be sound for the reasons I explained above.


Even if the layout wasn't a problem, the semantics of OnceCell prohibit splitting it. A OnceCell's value is either present/initialized or absent/uninitialized, and:

  • if the value is absent, then an as_slice_of_cells():
    • can't return Cells because their contents would be uninitialized memory.
    • can't return OnceCells because calling it twice before using any of them would allow you to overwrite previously set values (and also there's no way to cause setting each one of them to add up to setting the whole slice).
  • if the value is present, then it must not be mutated, unless the value itself is interior mutable, in which case you don't need OnceCell's help: OnceCell<Cell<[T]>> will do.

OnceCell is not like Cell and inherently cannot have the same operations. (It's much more like RefCell.)


It's unclear to me what you're hoping to show from this example, because there's no reason to be using Cell::swap here at all -- the much simpler thing is to use

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.