1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
// 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()
}
}
}