#![no_std]
#![forbid(non_ascii_idents)]
#![deny(
future_incompatible,
keyword_idents,
elided_lifetimes_in_paths,
meta_variable_misuse,
noop_method_call,
pointer_structural_match,
unused_lifetimes,
unused_qualifications,
unsafe_op_in_unsafe_fn,
clippy::undocumented_unsafe_blocks,
clippy::wildcard_dependencies,
clippy::debug_assert_with_mut_call,
clippy::empty_line_after_outer_attr,
clippy::panic,
clippy::unwrap_used,
clippy::redundant_field_names,
clippy::rest_pat_in_fully_bound_structs,
clippy::unneeded_field_pattern,
clippy::useless_let_if_seq
)]
#![warn(clippy::pedantic, missing_docs)]
use core::{marker::PhantomData, mem::MaybeUninit};
struct Invariant<'id>(PhantomData<fn(&'id ()) -> &'id ()>);
impl Invariant<'_> {
const LT: Self = Self(PhantomData);
}
pub struct Slot<'id, T, C>
where
C: Container<T>,
{
contents: C,
_value: PhantomData<T>,
_lifetime: Invariant<'id>,
}
pub type SafeSlot<'id, T> = Slot<'id, T, Option<T>>;
pub type LeakySlot<'id, T> = Slot<'id, T, MaybeUninit<T>>;
pub struct Proof<'id>(Invariant<'id>);
impl<T, C> Slot<'_, T, C>
where
C: Container<T>,
{
pub fn with<R>(f: impl for<'id> FnOnce(Slot<'id, T, C>) -> R) -> R {
f(Slot {
contents: C::empty(),
_value: PhantomData,
_lifetime: Invariant::LT,
})
}
}
impl<'id, C, T> Slot<'id, T, C>
where
C: Container<T>,
{
pub fn fill(&mut self, val: T) -> Proof<'id> {
self.contents.fill(val);
Proof(Invariant::LT)
}
#[allow(clippy::needless_pass_by_value)]
pub fn unlock(self, _proof: Proof<'id>) -> T {
unsafe { self.contents.unpack() }
}
}
pub unsafe trait Container<T> {
fn empty() -> Self;
fn fill(&mut self, val: T);
unsafe fn unpack(self) -> T;
}
unsafe impl<T> Container<T> for Option<T> {
fn empty() -> Self {
None
}
fn fill(&mut self, val: T) {
*self = Some(val);
}
unsafe fn unpack(self) -> T {
self.expect("trying to unpack None Container")
}
}
unsafe impl<T> Container<T> for MaybeUninit<T> {
fn empty() -> Self {
MaybeUninit::uninit()
}
fn fill(&mut self, val: T) {
self.write(val);
}
unsafe fn unpack(self) -> T {
unsafe { self.assume_init() }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[track_caller]
fn test_generic<C: Container<i32>>() {
assert_eq!(
Slot::<i32, C>::with(|mut slot| {
let proof = slot.fill(42);
slot.unlock(proof)
}),
42,
);
}
#[test]
fn safe() {
test_generic::<Option<i32>>();
}
#[test]
fn leaky() {
test_generic::<MaybeUninit<i32>>();
}
#[test]
fn leaky_is_free() {
assert_eq!(core::mem::size_of::<LeakySlot<'_, u64>>(), 8);
}
#[test]
fn safe_doesnt_leak() {
use core::sync::atomic::{AtomicUsize, Ordering::Relaxed};
struct ObservableDrop<'a>(&'a AtomicUsize);
impl Drop for ObservableDrop<'_> {
fn drop(&mut self) {
self.0.fetch_add(1, Relaxed);
}
}
let drop_count = AtomicUsize::new(0);
SafeSlot::with(|mut slot| {
slot.fill(ObservableDrop(&drop_count));
assert_eq!(drop_count.load(Relaxed), 0);
slot.fill(ObservableDrop(&drop_count));
assert_eq!(drop_count.load(Relaxed), 1);
});
assert_eq!(drop_count.load(Relaxed), 2);
}
}