erased_discriminant/
lib.rs

1//! [![github]](https://github.com/dtolnay/erased-discriminant) [![crates-io]](https://crates.io/crates/erased-discriminant) [![docs-rs]](https://docs.rs/erased-discriminant)
2//!
3//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
6//!
7//! This crate provides a `Discriminant` type that behaves like
8//! `core::mem::Discriminant<T>` but without the generic type parameter `T`.
9//! With this, we can build collections such as HashSet that contain
10//! discriminants from a mixture of different enum types.
11//!
12//! ```
13//! use erased_discriminant::Discriminant;
14//! use std::collections::HashSet;
15//!
16//! enum Enum {
17//!     A(i32),
18//!     B,
19//! }
20//!
21//! enum DifferentEnum {
22//!     A,
23//! }
24//!
25//! let mut set = HashSet::new();
26//! set.insert(Discriminant::of(&Enum::A(99)));
27//! set.insert(Discriminant::of(&Enum::B));
28//! set.insert(Discriminant::of(&DifferentEnum::A));
29//! assert_eq!(set.len(), 3);
30//! ```
31
32#![no_std]
33#![doc(html_root_url = "https://docs.rs/erased-discriminant/1.0.1")]
34#![deny(unsafe_op_in_unsafe_fn)]
35#![allow(clippy::doc_markdown, clippy::missing_safety_doc)]
36
37extern crate alloc;
38
39use alloc::boxed::Box;
40use core::any::TypeId;
41use core::fmt::{self, Debug};
42use core::hash::{Hash, Hasher};
43use core::mem::{self, MaybeUninit};
44
45/// A type-erased version of `core::mem::Discriminant<T>`.
46pub struct Discriminant {
47    data: MaybeUninit<*mut ()>,
48    vtable: &'static DiscriminantVTable,
49}
50
51impl Discriminant {
52    pub fn of<T>(value: &T) -> Self {
53        let discriminant = mem::discriminant(value);
54        let data = if small_discriminant::<T>() {
55            let mut data = MaybeUninit::<*mut ()>::uninit();
56            unsafe {
57                data.as_mut_ptr()
58                    .cast::<core::mem::Discriminant<T>>()
59                    .write(discriminant);
60            }
61            data
62        } else {
63            MaybeUninit::new(Box::into_raw(Box::new(discriminant)).cast::<()>())
64        };
65        Discriminant {
66            data,
67            vtable: &DiscriminantVTable {
68                eq: discriminant_eq::<T>,
69                hash: discriminant_hash::<T>,
70                clone: discriminant_clone::<T>,
71                debug: discriminant_debug::<T>,
72                drop: discriminant_drop::<T>,
73                type_id: typeid::of::<core::mem::Discriminant<T>>,
74            },
75        }
76    }
77}
78
79fn small_discriminant<T>() -> bool {
80    mem::size_of::<core::mem::Discriminant<T>>() <= mem::size_of::<*const ()>()
81}
82
83struct DiscriminantVTable {
84    eq: unsafe fn(this: &Discriminant, other: &Discriminant) -> bool,
85    hash: unsafe fn(this: &Discriminant, hasher: &mut dyn Hasher),
86    clone: unsafe fn(this: &Discriminant) -> Discriminant,
87    debug: unsafe fn(this: &Discriminant, formatter: &mut fmt::Formatter) -> fmt::Result,
88    drop: unsafe fn(this: &mut Discriminant),
89    type_id: fn() -> TypeId,
90}
91
92unsafe fn as_ref<T>(this: &Discriminant) -> &core::mem::Discriminant<T> {
93    unsafe {
94        &*if small_discriminant::<T>() {
95            this.data.as_ptr().cast::<core::mem::Discriminant<T>>()
96        } else {
97            this.data.assume_init().cast::<core::mem::Discriminant<T>>()
98        }
99    }
100}
101
102unsafe fn discriminant_eq<T>(this: &Discriminant, other: &Discriminant) -> bool {
103    (other.vtable.type_id)() == typeid::of::<core::mem::Discriminant<T>>()
104        && unsafe { as_ref::<T>(this) } == unsafe { as_ref::<T>(other) }
105}
106
107unsafe fn discriminant_hash<T>(this: &Discriminant, mut hasher: &mut dyn Hasher) {
108    typeid::of::<core::mem::Discriminant<T>>().hash(&mut hasher);
109    unsafe { as_ref::<T>(this) }.hash(&mut hasher);
110}
111
112unsafe fn discriminant_clone<T>(this: &Discriminant) -> Discriminant {
113    if small_discriminant::<T>() {
114        Discriminant {
115            data: this.data,
116            vtable: this.vtable,
117        }
118    } else {
119        let discriminant = unsafe { *this.data.assume_init().cast::<core::mem::Discriminant<T>>() };
120        Discriminant {
121            data: MaybeUninit::new(Box::into_raw(Box::new(discriminant)).cast::<()>()),
122            vtable: this.vtable,
123        }
124    }
125}
126
127unsafe fn discriminant_debug<T>(
128    this: &Discriminant,
129    formatter: &mut fmt::Formatter,
130) -> fmt::Result {
131    Debug::fmt(unsafe { as_ref::<T>(this) }, formatter)
132}
133
134unsafe fn discriminant_drop<T>(this: &mut Discriminant) {
135    if !small_discriminant::<T>() {
136        let _ =
137            unsafe { Box::from_raw(this.data.assume_init().cast::<core::mem::Discriminant<T>>()) };
138    }
139}
140
141impl Eq for Discriminant {}
142
143impl PartialEq for Discriminant {
144    fn eq(&self, other: &Self) -> bool {
145        unsafe { (self.vtable.eq)(self, other) }
146    }
147}
148
149impl Hash for Discriminant {
150    fn hash<H: Hasher>(&self, hasher: &mut H) {
151        unsafe { (self.vtable.hash)(self, hasher) };
152    }
153}
154
155impl Clone for Discriminant {
156    fn clone(&self) -> Self {
157        unsafe { (self.vtable.clone)(self) }
158    }
159}
160
161impl Debug for Discriminant {
162    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
163        unsafe { (self.vtable.debug)(self, formatter) }
164    }
165}
166
167impl Drop for Discriminant {
168    fn drop(&mut self) {
169        unsafe { (self.vtable.drop)(self) };
170    }
171}