matklad / once_cell

Rust library for single assignment cells and lazy statics without macros

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Typestate pattern

imbolc opened this issue · comments

Isn't it possible to get rid of Option in get() via typestate pattern (making the method available only for an initialized cell)?

You don't need typestate here -- if you statically know that &OnceCell<T> is initialized, you can use &T.

By "statically know" you mean static N: Lazy<i32> = Lazy::new(|| 42);? If so it's almost all of my use cases require runtime initialization (like reading an parsing a file). Then I should match on .get()s option each time which is inconvenient, or use &T without compile time guaranties of initialization.

Could you give a small example of your use-case? Basically, what I think should work is to match on .get once, and then thread the resulting &T, but it's hard to know if that'll work without a specific example.

The docs example will do: https://docs.rs/once_cell/1.8.0/once_cell/index.html#safe-initialization-of-global-data
A call of Logger::global() before INSTANCE.set(..) will panic.

Yeah, if that's a concern, it is possible to remove global function at all, and pass &'static Logger to any code which needs a logger. &'static is Copy and doesn't have lifeitems, so that should be pretty ergonomic.

Do you mean passing the reference as an argument? I thought the whole point of once_cell is having "globals". With passing through arguments why not just initialize the logger and use a usual reference?

Yeah, I mean passing as an argument. I think with any kind of typestate you'd have to pass an argument though?

I thought of this, but I missed type changing in set() :) Maybe it can be done somehow, maybe by unsafe, because type essentially stay the same?

use std::marker::PhantomData;

struct Cell<T, S> {
    payload: Option<T>,
    state: PhantomData<S>,
}

struct Empty;
struct Full;

impl<T, S> Cell<T, S> {
    fn new() -> Cell<T, Empty> {
        Cell {
            payload: None,
            state: PhantomData,
        }
    }
}

impl<T> Cell<T, Empty> {
    fn set(&self, payload: T) -> Cell<T, Full> {
        // I thought I could somehow mess with self here instead of returning a new instance
        Cell {
            payload: Some(payload),
            state: PhantomData,
        }
    }
}

impl<T> Cell<T, Full> {
    fn get(&self) -> &T {
        self.payload.as_ref().unwrap()
    }
}

fn main() {
    let empty = Cell::<i32, Empty>::new();
    let full = empty.set(1);
    assert_eq!(full.get(), &1);
}

I think at this point its best to experiment with this kinds of API in a different crate!