broption 0.1.1

For when you need more bro's in your ption's.
Documentation
// Copyright (C) 2023 Soni L.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Branded Option. for when you need bro's in your ption's.
//!
//! Possibly unsound. Designed for use with `selfref`.
//!
//! # Examples
//!
//! ```rust
//! use broption::BOption;
//!
//! // define a resource that needs a separate initialization step.
//! struct Foo<'bro> {
//!   name: BOption<'bro, Box<str>>,
//! }
//!
//! // define a context wrapper for our resource.
//! struct Ctx;
//! impl broption::Wrapper for Ctx {
//!   type Kind<'bro> = Foo<'bro>;
//! }
//!
//! // create an "initialization context".
//! let initialized = BOption::factory::<Ctx, _>(|factory| {
//!   // create an uninitialized resource.
//!   let mut maybeinit = Foo {
//!     name: factory.new_none(),
//!   };
//!   // initialize the resource.
//!   factory.init(&mut maybeinit.name, Box::from("hello"));
//!   // return the hopefully-initialized resource.
//!   maybeinit
//! });
//! // use the initialized resource
//! assert_eq!(&**initialized.name, "hello");
//! ```
//!
//! Failing to correctly initialize the resource panics:
//!
//! ```rust,should_panic
//! use broption::BOption;
//!
//! // define a resource that needs a separate initialization step.
//! struct Foo<'bro> {
//!   name: BOption<'bro, Box<str>>,
//! }
//!
//! // define a context wrapper for our resource.
//! struct Ctx;
//! impl broption::Wrapper for Ctx {
//!   type Kind<'bro> = Foo<'bro>;
//! }
//!
//! // create an "initialization context".
//! let initialized = BOption::factory::<Ctx, _>(|factory| {
//!   // create an uninitialized resource.
//!   let mut maybeinit = Foo {
//!     name: factory.new_none(),
//!   };
//!   // return the uninitialized resource.
//!   maybeinit
//! });
//! ```

use core::marker::PhantomData;
use core::ops::Deref;
use core::ops::DerefMut;

#[repr(transparent)]
pub struct BOption<'id, T>(Option<T>, PhantomData<fn(&'id ())->&'id ()>);

impl<T: Copy> Copy for BOption<'static, T> {}
impl<T: Clone> Clone for BOption<'static, T> {
    fn clone(&self) -> Self {
        let Self(option, id) = self;
        Self(option.clone(), *id)
    }
}

pub trait Wrapper {
    type Kind<'a>;
}

pub struct Factory<'id> {
    count: usize,
    id: PhantomData<fn(&'id ())->&'id ()>,
}

impl<'id> Factory<'id> {
    pub fn new_none<T>(&mut self) -> BOption<'id, T> {
        self.count += 1;
        BOption(None, self.id)
    }

    pub fn init<'a, T>(
        &mut self,
        boption: &'a mut BOption<'id, T>,
        value: T,
    ) -> &'a mut T {
        let was_none = boption.0.is_none();
        let value = boption.0.insert(value);
        if was_none {
            self.count -= 1;
        }
        value
    }
}

impl BOption<'static, ()> {
    pub fn factory<T, F>(f: F) -> T::Kind<'static>
    where
        T: Wrapper,
        F: for<'id> FnOnce(&mut Factory<'id>) -> T::Kind<'id>,
    {
        let mut factory = Factory {
            count: 0,
            id: PhantomData, // inferred 'static
        };
        let res = f(&mut factory);
        assert_eq!(factory.count, 0);
        res
    }
}

impl<T> BOption<'static, T> {
    pub fn new_init(t: T) -> Self {
        Self(Some(t), PhantomData)
    }
}

impl<T> Deref for BOption<'static, T> {
    type Target = T;
    fn deref(&self) -> &T {
        // SAFETY: the safety of this is very non-local.
        // you can't create a BOption without going through Factory, which keeps
        // track of uninitialized BOptions.
        // you can't swap a Factory with another Factory since you can't get the
        // invariant 'id to converge.
        // finally, you can only gain access to a 'static BOption if it's
        // already initialized. oh and you can only swap it for other
        // initialized BOptions.
        unsafe {
            self.0.as_ref().unwrap_unchecked()
        }
    }
}

impl<T> DerefMut for BOption<'static, T> {
    fn deref_mut(&mut self) -> &mut T {
        // SAFETY: the safety of this is very non-local.
        // you can't create a BOption without going through Factory, which keeps
        // track of uninitialized BOptions.
        // you can't swap a Factory with another Factory since you can't get the
        // invariant 'id to converge.
        // finally, you can only gain access to a 'static BOption if it's
        // already initialized. oh and you can only swap it for other
        // initialized BOptions.
        unsafe {
            self.0.as_mut().unwrap_unchecked()
        }
    }
}