#![feature(const_trait_impl)]
#![feature(generic_const_exprs)]
#![feature(const_cmp)]
#![feature(transmutability)]
#![allow(incomplete_features)]
use core::marker::PhantomData;
use core::mem::{Assume, TransmuteFrom};
const TRANSMUTATION_ASSUMPTION: Assume = Assume {
alignment: false,
lifetimes: false,
safety: true,
validity: true,
};
mod __layout {
use core::marker::PhantomData;
use core::mem::{align_of, size_of};
use crate::CompactRepr;
pub(crate) struct LayoutInvariant<R, T: ?Sized>(PhantomData<(R, T)>);
impl<R, T> LayoutInvariant<R, T>
where
T: CompactRepr<R>,
{
pub(crate) const CHECK: () = {
assert!(size_of::<T>() == size_of::<R>());
assert!(align_of::<T>() == align_of::<R>());
};
}
}
pub const unsafe trait CompactRepr<R>: Copy + Sized {
const UNUSED_SENTINEL: R;
}
#[cfg(feature = "macros")]
pub use compact_option_proc_macro::compact_option;
#[repr(transparent)]
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct CompactOption<R, T: CompactRepr<R>> {
raw_value: R,
_marker: PhantomData<T>,
}
impl<R: Copy, T: CompactRepr<R> + Copy> Copy for CompactOption<R, T> {}
impl<R, T> CompactOption<R, T>
where
R: Copy + PartialEq,
T: CompactRepr<R>,
{
pub const NONE: Self = {
let () = __layout::LayoutInvariant::<R, T>::CHECK;
Self {
raw_value: T::UNUSED_SENTINEL,
_marker: PhantomData,
}
};
pub fn some(value: T) -> Self
where
T: CompactRepr<R>,
R: TransmuteFrom<T, { TRANSMUTATION_ASSUMPTION }>,
{
let () = __layout::LayoutInvariant::<R, T>::CHECK;
Self {
raw_value: unsafe {
<R as TransmuteFrom<T, { TRANSMUTATION_ASSUMPTION }>>::transmute(value)
},
_marker: PhantomData,
}
}
pub const fn is_none(self) -> bool
where
R: [const] PartialEq,
{
self.raw_value == T::UNUSED_SENTINEL
}
pub const fn is_some(self) -> bool
where
R: [const] PartialEq,
{
!self.is_none()
}
pub fn try_unwrap(self) -> Option<T>
where
T: TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>,
{
if self.raw_value == T::UNUSED_SENTINEL {
None
} else {
debug_assert!(
self.raw_value != T::UNUSED_SENTINEL,
"CompactOption::try_unwrap: raw must differ from UNUSED_SENTINEL"
);
Some(unsafe { self.unwrap_unchecked() })
}
}
pub fn unwrap(self) -> T
where
T: TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>,
{
match self.try_unwrap() {
Some(t) => t,
None => panic!("called `CompactOption::unwrap` on a `NONE` value"),
}
}
pub fn expect(self, msg: &str) -> T
where
T: TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>,
{
match self.try_unwrap() {
Some(t) => t,
None => panic!("{msg}"),
}
}
pub fn map<U, F>(self, f: F) -> Option<U>
where
F: FnOnce(T) -> U,
T: TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>,
{
self.try_unwrap().map(f)
}
pub fn and_then<U, F>(self, f: F) -> Option<U>
where
F: FnOnce(T) -> Option<U>,
T: TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>,
{
self.try_unwrap().and_then(f)
}
pub unsafe fn unwrap_unchecked(self) -> T
where
T: TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>,
{
debug_assert!(
self.raw_value != T::UNUSED_SENTINEL,
"CompactOption::unwrap_unchecked: self must not be NONE (raw != UNUSED_SENTINEL)"
);
unsafe { <T as TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>>::transmute(self.raw_value) }
}
}
#[cfg(test)]
mod fixtures {
use crate::{CompactOption, CompactRepr};
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum SmallEnum {
Var1 = 0,
Var2 = 1,
}
unsafe impl const CompactRepr<u8> for SmallEnum {
const UNUSED_SENTINEL: u8 = 0xFF;
}
pub(crate) type OptSmall = CompactOption<u8, SmallEnum>;
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct ByteSlot(pub u8);
unsafe impl const CompactRepr<u8> for ByteSlot {
const UNUSED_SENTINEL: u8 = 0xFE;
}
pub(crate) type OptByte = CompactOption<u8, ByteSlot>;
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct Handle(pub u32);
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct Id(pub u32);
unsafe impl const CompactRepr<Handle> for Id {
const UNUSED_SENTINEL: Handle = Handle(u32::MAX);
}
pub(crate) type OptId = CompactOption<Handle, Id>;
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum BadSentinel {
A = 0,
}
unsafe impl const CompactRepr<u8> for BadSentinel {
const UNUSED_SENTINEL: u8 = 0;
}
pub(crate) type OptBad = CompactOption<u8, BadSentinel>;
pub(crate) const NONE_IS_NONE: bool = OptSmall::NONE.is_none();
pub(crate) const NONE_NOT_SOME: bool = !OptSmall::NONE.is_some();
}
#[cfg(test)]
mod proptests;
#[cfg(test)]
use core::hash::{Hash, Hasher};
#[cfg(test)]
use fixtures::{BadSentinel, ByteSlot, Id, OptBad, OptByte, OptId, OptSmall, SmallEnum};
#[cfg(test)]
#[test]
fn const_predicates_on_none() {
const { assert!(fixtures::NONE_IS_NONE) };
const { assert!(fixtures::NONE_NOT_SOME) };
assert!(OptSmall::some(SmallEnum::Var1).is_some());
assert!(!OptSmall::some(SmallEnum::Var1).is_none());
assert!(OptSmall::some(SmallEnum::Var2).is_some());
assert!(!OptSmall::some(SmallEnum::Var2).is_none());
}
#[cfg(test)]
#[test]
fn repr_u8_enum_roundtrip_and_combinators() {
let foo = OptSmall::some(SmallEnum::Var1);
assert_eq!(foo.raw_value, SmallEnum::Var1 as u8);
assert_eq!(foo.try_unwrap(), Some(SmallEnum::Var1));
assert_eq!(OptSmall::some(SmallEnum::Var1).unwrap(), SmallEnum::Var1);
let bar = OptSmall::some(SmallEnum::Var2);
assert_eq!(bar.map(|x| x as u8), Some(1u8));
assert_eq!(bar.and_then(Some), Some(SmallEnum::Var2));
assert_eq!(bar.and_then(|_| None::<()>), None);
assert_eq!(OptSmall::NONE.try_unwrap(), None);
assert_eq!(
OptSmall::some(SmallEnum::Var1).expect("some"),
SmallEnum::Var1
);
unsafe {
assert_eq!(
OptSmall::some(SmallEnum::Var2).unwrap_unchecked(),
SmallEnum::Var2
);
}
}
#[cfg(test)]
#[test]
fn map_and_then_skip_closure_on_none() {
assert_eq!(OptSmall::NONE.map::<(), _>(|_| panic!("map on NONE")), None);
assert_eq!(
OptSmall::NONE.and_then::<(), _>(|_| panic!("and_then on NONE")),
None
);
}
#[cfg(test)]
#[test]
fn transparent_struct_payload_roundtrip() {
let b = ByteSlot(7);
let o = OptByte::some(b);
assert_eq!(o.try_unwrap(), Some(ByteSlot(7)));
assert_eq!(o.unwrap(), ByteSlot(7));
}
#[cfg(test)]
#[test]
fn non_integer_handle_roundtrip() {
let id = Id(42);
let o = OptId::some(id);
assert_eq!(o.try_unwrap(), Some(Id(42)));
assert_eq!(o.unwrap().0, 42);
}
#[cfg(test)]
#[test]
fn sentinel_collision_some_equals_none() {
let none = OptBad::NONE;
let some_a = OptBad::some(BadSentinel::A);
assert_eq!(none.raw_value, some_a.raw_value);
assert_eq!(none, some_a);
assert!(none.is_none());
assert!(!some_a.is_some());
assert_eq!(some_a.try_unwrap(), None);
}
#[cfg(test)]
#[test]
fn derives_clone_partial_eq_hash_debug() {
assert_eq!(OptSmall::NONE, OptSmall::NONE);
assert_eq!(
OptSmall::some(SmallEnum::Var1),
OptSmall::some(SmallEnum::Var1)
);
assert_ne!(
OptSmall::some(SmallEnum::Var1),
OptSmall::some(SmallEnum::Var2)
);
let a = OptSmall::some(SmallEnum::Var1);
let b = a;
assert_eq!(a, b);
assert_eq!(a.clone(), b);
assert_eq!(OptSmall::NONE.clone(), OptSmall::NONE);
assert_ne!(a, OptSmall::NONE);
let mut h1 = std::collections::hash_map::DefaultHasher::new();
let mut h2 = std::collections::hash_map::DefaultHasher::new();
a.hash(&mut h1);
b.hash(&mut h2);
assert_eq!(h1.finish(), h2.finish());
let s = format!("{a:?}");
assert!(s.contains("CompactOption"));
}
#[cfg(test)]
#[test]
#[should_panic(expected = "called `CompactOption::unwrap` on a `NONE` value")]
fn none_unwrap_panics() {
let _ = OptSmall::NONE.unwrap();
}
#[cfg(test)]
#[test]
#[should_panic(expected = "empty")]
fn none_expect_panics() {
let _ = OptSmall::NONE.expect("empty");
}
#[cfg(test)]
#[test]
#[ignore = "undefined behavior; run under Miri with --ignored"]
fn miri_ub_unwrap_unchecked_on_none() {
unsafe {
let _ = OptSmall::NONE.unwrap_unchecked();
}
}