matklad / once_cell

Rust library for single assignment cells and lazy statics without macros

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Understanding "once" in the presence of `take()`

dherman opened this issue · comments

I got confused when I came across the take() method. Specifically, the README says (emphasis mine):

OnceCell might store arbitrary non-Copy types, can be assigned to at most once and provide direct access to the stored contents.

But with take(), it's easy to assign to a OnceCell an arbitrary number of times:

let mut cell: OnceCell<u32> = OnceCell::new();

for i in 0..1000 {
    cell.set(i).unwrap();
    assert_eq!(cell.take(), Some(i));
}

This left me confused about what exactly the core abstraction of this API is aiming to accomplish. I suspect this is probably just a documentation issue? But can you help me understand what "once-ness" means given that you can actually assign to the cell an arbitrary number of times?

Yeah, its the documentation issue, the eternal struggle between being helpful and being pedantically correct. "Assigned at most once" is the right intuition here, but, as with all interior mutability types, if you have &mut OnceCell<T>, you can do whatever.

Compare it with, eg, std::sync::Mutex, which claims that "he data can only be accessed through the RAII guards" and yet has get_mut which yields the direct access to data without any locks.

I think the right solution here is to add extra explanations to all &mut self methods (take, get_mut, DerefMut for Lazy), while keeping the intuition building "once" tagline intact.

A PR would be lovely! I'll tackle this myself some day if no one beats me to it!.

A helpful example to add to the take docs:

let mut cell: OnceCell<u32> = OnceCell::new();
cell.set(92).unwrap();
cell = OnceCell::new(); // don't need take to overwrite the value

I think the right solution here is to add extra explanations to all &mut self methods (take, get_mut, DerefMut for Lazy), while keeping the intuition building "once" tagline intact.

That sounds exactly right to me -- don't muddy the general intuition with pedantry, but clarify the corner cases where they show up in the docs. (Anecdotally, in my case this doc strategy would be perfect, because it was exactly when I ran into the take() method that I got confused.)

A PR would be lovely!

Let me see if I can hack something up quickly…

(Sorry for the double-commit, I accidentally pushed to master on my fork at first. I reset master and pushed to a branch instead.)