1#![feature(const_trait_impl)]
2#![feature(generic_const_exprs)]
3#![feature(const_cmp)]
4#![feature(transmutability)]
5#![allow(incomplete_features)]
6
7use core::marker::PhantomData;
22use core::mem::{Assume, TransmuteFrom};
23
24const TRANSMUTATION_ASSUMPTION: Assume = Assume {
25 alignment: false,
26 lifetimes: false,
27 safety: true,
28 validity: true,
29};
30
31mod __layout {
32 use core::marker::PhantomData;
33 use core::mem::{align_of, size_of};
34
35 use crate::CompactRepr;
36
37 pub(crate) struct LayoutInvariant<R, T: ?Sized>(PhantomData<(R, T)>);
38
39 impl<R, T> LayoutInvariant<R, T>
40 where
41 T: CompactRepr<R>,
42 {
43 pub(crate) const CHECK: () = {
44 assert!(size_of::<T>() == size_of::<R>());
45 assert!(align_of::<T>() == align_of::<R>());
46 };
47 }
48}
49
50pub const unsafe trait CompactRepr<R>: Copy + Sized {
82 const UNUSED_SENTINEL: R;
91}
92
93#[cfg(feature = "macros")]
95pub use compact_option_proc_macro::compact_option;
96
97#[repr(transparent)]
142#[derive(Clone, Eq, PartialEq, Hash, Debug)]
143pub struct CompactOption<R, T: CompactRepr<R>> {
144 raw_value: R,
145 _marker: PhantomData<T>,
146}
147
148impl<R: Copy, T: CompactRepr<R> + Copy> Copy for CompactOption<R, T> {}
149
150impl<R, T> CompactOption<R, T>
151where
152 R: Copy + PartialEq,
153 T: CompactRepr<R>,
154{
155 pub const NONE: Self = {
161 let () = __layout::LayoutInvariant::<R, T>::CHECK;
162 Self {
163 raw_value: T::UNUSED_SENTINEL,
164 _marker: PhantomData,
165 }
166 };
167
168 pub fn some(value: T) -> Self
182 where
183 T: CompactRepr<R>,
184 R: TransmuteFrom<T, { TRANSMUTATION_ASSUMPTION }>,
185 {
186 let () = __layout::LayoutInvariant::<R, T>::CHECK;
187 Self {
188 raw_value: unsafe {
189 <R as TransmuteFrom<T, { TRANSMUTATION_ASSUMPTION }>>::transmute(value)
190 },
191 _marker: PhantomData,
192 }
193 }
194
195 pub const fn is_none(self) -> bool
197 where
198 R: [const] PartialEq,
199 {
200 self.raw_value == T::UNUSED_SENTINEL
201 }
202
203 pub const fn is_some(self) -> bool
205 where
206 R: [const] PartialEq,
207 {
208 !self.is_none()
209 }
210
211 pub fn try_unwrap(self) -> Option<T>
214 where
215 T: TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>,
216 {
217 if self.raw_value == T::UNUSED_SENTINEL {
218 None
219 } else {
220 debug_assert!(
221 self.raw_value != T::UNUSED_SENTINEL,
222 "CompactOption::try_unwrap: raw must differ from UNUSED_SENTINEL"
223 );
224 Some(unsafe { self.unwrap_unchecked() })
227 }
228 }
229
230 pub fn unwrap(self) -> T
232 where
233 T: TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>,
234 {
235 match self.try_unwrap() {
236 Some(t) => t,
237 None => panic!("called `CompactOption::unwrap` on a `NONE` value"),
238 }
239 }
240
241 pub fn expect(self, msg: &str) -> T
243 where
244 T: TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>,
245 {
246 match self.try_unwrap() {
247 Some(t) => t,
248 None => panic!("{msg}"),
249 }
250 }
251
252 pub fn map<U, F>(self, f: F) -> Option<U>
254 where
255 F: FnOnce(T) -> U,
256 T: TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>,
257 {
258 self.try_unwrap().map(f)
259 }
260
261 pub fn and_then<U, F>(self, f: F) -> Option<U>
263 where
264 F: FnOnce(T) -> Option<U>,
265 T: TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>,
266 {
267 self.try_unwrap().and_then(f)
268 }
269
270 pub unsafe fn unwrap_unchecked(self) -> T
274 where
275 T: TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>,
276 {
277 debug_assert!(
278 self.raw_value != T::UNUSED_SENTINEL,
279 "CompactOption::unwrap_unchecked: self must not be NONE (raw != UNUSED_SENTINEL)"
280 );
281 unsafe { <T as TransmuteFrom<R, { TRANSMUTATION_ASSUMPTION }>>::transmute(self.raw_value) }
282 }
283}
284
285#[cfg(test)]
286mod fixtures {
287 use crate::{CompactOption, CompactRepr};
288
289 #[repr(u8)]
291 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
292 pub(crate) enum SmallEnum {
293 Var1 = 0,
294 Var2 = 1,
295 }
296
297 unsafe impl const CompactRepr<u8> for SmallEnum {
298 const UNUSED_SENTINEL: u8 = 0xFF;
299 }
300
301 pub(crate) type OptSmall = CompactOption<u8, SmallEnum>;
302
303 #[repr(transparent)]
305 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
306 pub(crate) struct ByteSlot(pub u8);
307
308 unsafe impl const CompactRepr<u8> for ByteSlot {
309 const UNUSED_SENTINEL: u8 = 0xFE;
310 }
311
312 pub(crate) type OptByte = CompactOption<u8, ByteSlot>;
313
314 #[repr(transparent)]
316 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
317 pub(crate) struct Handle(pub u32);
318
319 #[repr(transparent)]
320 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
321 pub(crate) struct Id(pub u32);
322
323 unsafe impl const CompactRepr<Handle> for Id {
324 const UNUSED_SENTINEL: Handle = Handle(u32::MAX);
325 }
326
327 pub(crate) type OptId = CompactOption<Handle, Id>;
328
329 #[repr(u8)]
331 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
332 pub(crate) enum BadSentinel {
333 A = 0,
334 }
335
336 unsafe impl const CompactRepr<u8> for BadSentinel {
337 const UNUSED_SENTINEL: u8 = 0;
338 }
339
340 pub(crate) type OptBad = CompactOption<u8, BadSentinel>;
341
342 pub(crate) const NONE_IS_NONE: bool = OptSmall::NONE.is_none();
343 pub(crate) const NONE_NOT_SOME: bool = !OptSmall::NONE.is_some();
344}
345
346#[cfg(test)]
347mod proptests;
348
349#[cfg(test)]
350use core::hash::{Hash, Hasher};
351
352#[cfg(test)]
353use fixtures::{BadSentinel, ByteSlot, Id, OptBad, OptByte, OptId, OptSmall, SmallEnum};
354
355#[cfg(test)]
356#[test]
357fn const_predicates_on_none() {
358 const { assert!(fixtures::NONE_IS_NONE) };
359 const { assert!(fixtures::NONE_NOT_SOME) };
360 assert!(OptSmall::some(SmallEnum::Var1).is_some());
361 assert!(!OptSmall::some(SmallEnum::Var1).is_none());
362 assert!(OptSmall::some(SmallEnum::Var2).is_some());
363 assert!(!OptSmall::some(SmallEnum::Var2).is_none());
364}
365
366#[cfg(test)]
367#[test]
368fn repr_u8_enum_roundtrip_and_combinators() {
369 let foo = OptSmall::some(SmallEnum::Var1);
370 assert_eq!(foo.raw_value, SmallEnum::Var1 as u8);
371 assert_eq!(foo.try_unwrap(), Some(SmallEnum::Var1));
372 assert_eq!(OptSmall::some(SmallEnum::Var1).unwrap(), SmallEnum::Var1);
373
374 let bar = OptSmall::some(SmallEnum::Var2);
375 assert_eq!(bar.map(|x| x as u8), Some(1u8));
376 assert_eq!(bar.and_then(Some), Some(SmallEnum::Var2));
377 assert_eq!(bar.and_then(|_| None::<()>), None);
378
379 assert_eq!(OptSmall::NONE.try_unwrap(), None);
380 assert_eq!(
381 OptSmall::some(SmallEnum::Var1).expect("some"),
382 SmallEnum::Var1
383 );
384
385 unsafe {
386 assert_eq!(
387 OptSmall::some(SmallEnum::Var2).unwrap_unchecked(),
388 SmallEnum::Var2
389 );
390 }
391}
392
393#[cfg(test)]
394#[test]
395fn map_and_then_skip_closure_on_none() {
396 assert_eq!(OptSmall::NONE.map::<(), _>(|_| panic!("map on NONE")), None);
397 assert_eq!(
398 OptSmall::NONE.and_then::<(), _>(|_| panic!("and_then on NONE")),
399 None
400 );
401}
402
403#[cfg(test)]
404#[test]
405fn transparent_struct_payload_roundtrip() {
406 let b = ByteSlot(7);
407 let o = OptByte::some(b);
408 assert_eq!(o.try_unwrap(), Some(ByteSlot(7)));
409 assert_eq!(o.unwrap(), ByteSlot(7));
410}
411
412#[cfg(test)]
413#[test]
414fn non_integer_handle_roundtrip() {
415 let id = Id(42);
416 let o = OptId::some(id);
417 assert_eq!(o.try_unwrap(), Some(Id(42)));
418 assert_eq!(o.unwrap().0, 42);
419}
420
421#[cfg(test)]
422#[test]
423fn sentinel_collision_some_equals_none() {
424 let none = OptBad::NONE;
425 let some_a = OptBad::some(BadSentinel::A);
426 assert_eq!(none.raw_value, some_a.raw_value);
427 assert_eq!(none, some_a);
428 assert!(none.is_none());
429 assert!(!some_a.is_some());
430 assert_eq!(some_a.try_unwrap(), None);
431}
432
433#[cfg(test)]
434#[test]
435fn derives_clone_partial_eq_hash_debug() {
436 assert_eq!(OptSmall::NONE, OptSmall::NONE);
437 assert_eq!(
438 OptSmall::some(SmallEnum::Var1),
439 OptSmall::some(SmallEnum::Var1)
440 );
441 assert_ne!(
442 OptSmall::some(SmallEnum::Var1),
443 OptSmall::some(SmallEnum::Var2)
444 );
445
446 let a = OptSmall::some(SmallEnum::Var1);
447 let b = a;
448 assert_eq!(a, b);
449 assert_eq!(a.clone(), b);
450 assert_eq!(OptSmall::NONE.clone(), OptSmall::NONE);
451 assert_ne!(a, OptSmall::NONE);
452 let mut h1 = std::collections::hash_map::DefaultHasher::new();
453 let mut h2 = std::collections::hash_map::DefaultHasher::new();
454 a.hash(&mut h1);
455 b.hash(&mut h2);
456 assert_eq!(h1.finish(), h2.finish());
457 let s = format!("{a:?}");
458 assert!(s.contains("CompactOption"));
459}
460
461#[cfg(test)]
462#[test]
463#[should_panic(expected = "called `CompactOption::unwrap` on a `NONE` value")]
464fn none_unwrap_panics() {
465 let _ = OptSmall::NONE.unwrap();
466}
467
468#[cfg(test)]
469#[test]
470#[should_panic(expected = "empty")]
471fn none_expect_panics() {
472 let _ = OptSmall::NONE.expect("empty");
473}
474
475#[cfg(test)]
478#[test]
479#[ignore = "undefined behavior; run under Miri with --ignored"]
480fn miri_ub_unwrap_unchecked_on_none() {
481 unsafe {
482 let _ = OptSmall::NONE.unwrap_unchecked();
483 }
484}