const_assoc/
lib.rs

1#![no_std]
2#![allow(private_bounds)]
3
4//! A `no_std`-compatible, const-capable associative array with minimal or no runtime overhead.
5//!
6//! Currently, keys are limited to enums with a primitive representation. In the future,
7//! it might be possible to support other types, possibly at the expense of not exposing
8//! `const`-qualified methods for these key types or some runtime overhead.
9//!
10//! # Example
11//! ```
12//! use const_assoc::{assoc, PrimitiveEnum};
13//!
14//! #[repr(u8)]
15//! #[derive(Copy, Clone, PrimitiveEnum)]
16//! enum Letter {
17//!     A,
18//!     B,
19//!     C,
20//! }
21//!
22//! let letters = assoc! {
23//!     Letter::A => 'a',
24//!     Letter::B => 'b',
25//!     Letter::C => 'c',
26//! };
27//!
28//! assert_eq!(letters[Letter::A], 'a');
29//! assert_eq!(letters[Letter::C], 'c');
30//! ```
31
32mod utils;
33
34use crate::utils::{
35    assume_init_array, into_usize, transmute_safe, ConstIntoUSize, ConstUSize, Is, IsConstUSize,
36    TransmuteSafe,
37};
38use core::marker::PhantomData;
39use core::mem::MaybeUninit;
40use core::ops::{Index, IndexMut};
41use derive_where::derive_where;
42
43// Re-export `const_default::ConstDefault`.
44pub use const_default::ConstDefault;
45
46// Re-export the derive macro for `PrimitiveEnum`.
47pub use const_assoc_derive::PrimitiveEnum;
48
49/// Provides an easy, const-friendly way to construct a new [`Assoc`] instance.
50///
51/// # Example
52/// ```
53/// use const_assoc::{assoc, PrimitiveEnum};
54///
55/// #[repr(u8)]
56/// #[derive(Copy, Clone, PrimitiveEnum)]
57/// enum Letter {
58///     A,
59///     B,
60///     C,
61/// }
62///
63/// let letters = assoc! {
64///     Letter::A => 'a',
65///     Letter::B => 'b',
66///     Letter::C => 'c',
67/// };
68/// ```
69#[macro_export]
70macro_rules! assoc {
71    ($($key:expr => $value:expr),* $(,)?) => {
72        {
73            let phantom_values = $crate::assoc_macro_private::PhantomArray::new(&[$($value),*]);
74
75            if $crate::assoc_macro_private::has_duplicate_keys(&[$($key),*], phantom_values) {
76                panic!("A `ConstArrayMap` cannot have two values with identical keys.");
77            }
78
79            let mut map = $crate::Assoc::<_, _>::new_uninit();
80
81            $(
82                *map.const_get_mut($key) = ::core::mem::MaybeUninit::new($value);
83            )*
84
85            // SAFETY:
86            // - `has_duplicate_keys` ensures that the code won't compile if
87            //   there are not as many keys as values.
88            // - `has_duplicate_keys` checks for duplicate keys and panics
89            //   otherwise.
90            //
91            // Thus, since there are exactly as many keys as values and no
92            // duplicate keys, each key corresponds to exactly one value and
93            // each value corresponds to a unique key, which implies that
94            // `map` must have been fully initialized.
95            unsafe { map.assume_init() }
96        }
97    };
98}
99
100#[doc(hidden)]
101pub mod assoc_macro_private {
102    use crate::{key_to_index, Key, KeyImpl};
103    use core::marker::PhantomData;
104
105    pub const fn has_duplicate_keys<K: Key, V, const N: usize>(
106        keys: &[K; N],
107        _values: PhantomArray<V, N>,
108    ) -> bool
109    where
110        K::Impl: KeyImpl<Storage<V> = [V; N]>,
111    {
112        let mut i = 0;
113
114        while i < N {
115            let mut j = i + 1;
116
117            while j < N {
118                if key_to_index(keys[i]) == key_to_index(keys[j]) {
119                    return true;
120                }
121
122                j += 1;
123            }
124
125            i += 1;
126        }
127
128        false
129    }
130
131    pub struct PhantomArray<T, const N: usize>(PhantomData<[T; N]>);
132
133    impl<T, const N: usize> PhantomArray<T, N> {
134        #[inline(always)]
135        pub const fn new(_array: &[T; N]) -> Self {
136            PhantomArray(PhantomData)
137        }
138    }
139}
140
141/// Associates keys with values with minimal or no runtime overhead.
142#[repr(transparent)]
143pub struct Assoc<K: Key, V> {
144    storage: <K::Impl as KeyImpl>::Storage<V>,
145}
146
147impl<K: Key, V: ConstDefault> ConstDefault for Assoc<K, V>
148where
149    <K::Impl as KeyImpl>::Storage<V>: ConstDefault,
150{
151    const DEFAULT: Self = Self {
152        storage: <<K::Impl as KeyImpl>::Storage<V>>::DEFAULT,
153    };
154}
155
156impl<K: Key, V: ConstDefault> Default for Assoc<K, V>
157where
158    <K::Impl as KeyImpl>::Storage<V>: Default,
159{
160    fn default() -> Self {
161        Self {
162            storage: <<K::Impl as KeyImpl>::Storage<V> as Default>::default(),
163        }
164    }
165}
166
167impl<K: Key, V, const N: usize> Assoc<K, V>
168where
169    K::Impl: KeyImpl<Storage<V> = [V; N]>,
170{
171    pub const LEN: usize = N;
172
173    pub const fn from_values(values: [V; N]) -> Self {
174        Self { storage: values }
175    }
176
177    #[inline(always)]
178    pub const fn len(&self) -> usize {
179        Self::LEN
180    }
181
182    #[inline(always)]
183    pub const fn is_empty(&self) -> bool {
184        self.len() == 0
185    }
186
187    /// Returns a reference to the value associated with the given key.
188    #[inline(always)]
189    pub fn get(&self, key: K) -> &V {
190        let idx = key_to_index(key);
191        // SAFETY: The invariant of `KeyImpl` guarantees that
192        // `idx` is always less than `self.storage.len()`.
193        unsafe { self.storage.get_unchecked(idx) }
194    }
195
196    /// Returns a reference to the value associated with the given key.
197    ///
198    /// This version does bounds-checking therefore can be used in const
199    /// contexts, unlike `get`.
200    #[inline(always)]
201    pub const fn const_get(&self, key: K) -> &V {
202        let idx = key_to_index(key);
203        &self.storage[idx]
204    }
205
206    /// Returns a mutable reference to the value associated with the given key.
207    #[inline(always)]
208    pub fn get_mut(&mut self, key: K) -> &mut V {
209        let idx = key_to_index(key);
210        // SAFETY: The invariant of `KeyImpl` guarantees that
211        // `idx` is always less than `self.storage.len()`.
212        unsafe { self.storage.get_unchecked_mut(idx) }
213    }
214
215    /// Returns a mutable reference to the value associated with the given key.
216    ///
217    /// This version does bounds-checking, therefore can be used in const
218    /// contexts, unlike `get`.
219    #[inline(always)]
220    pub const fn const_get_mut(&mut self, key: K) -> &mut V {
221        let idx = key_to_index(key);
222        &mut self.storage[idx]
223    }
224
225    /// Takes `self` by value and returns an iterator over all the values
226    /// stored in this map.
227    pub fn into_values(self) -> impl Iterator<Item = V> {
228        self.storage.into_iter()
229    }
230
231    /// Returns an iterator over shared references to all values stored in this
232    /// map in arbitrary order.
233    pub fn values(&self) -> impl Iterator<Item = &V> {
234        self.storage.iter()
235    }
236
237    /// Returns an iterator over mutable references to all values stored in
238    /// this map in arbitrary order.
239    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
240        self.storage.iter_mut()
241    }
242}
243
244impl<K: Key, V, const N: usize> Index<K> for Assoc<K, V>
245where
246    K::Impl: KeyImpl<Storage<V> = [V; N]>,
247{
248    type Output = V;
249
250    #[inline(always)]
251    fn index(&self, index: K) -> &Self::Output {
252        self.get(index)
253    }
254}
255
256impl<K: Key, V, const N: usize> IndexMut<K> for Assoc<K, V>
257where
258    K::Impl: KeyImpl<Storage<V> = [V; N]>,
259{
260    #[inline(always)]
261    fn index_mut(&mut self, index: K) -> &mut Self::Output {
262        self.get_mut(index)
263    }
264}
265
266impl<K: Key, V, const N: usize> Assoc<K, MaybeUninit<V>>
267where
268    K::Impl: KeyImpl<Storage<V> = [V; N]>,
269    K::Impl: KeyImpl<Storage<MaybeUninit<V>> = [MaybeUninit<V>; N]>,
270{
271    pub const fn new_uninit() -> Self {
272        // SAFETY: we are transmute an uninitialized array to an array with
273        // uninitialized elements, which is always safe.
274        let storage: [MaybeUninit<V>; N] = unsafe { MaybeUninit::uninit().assume_init() };
275        Self { storage }
276    }
277
278    /// Interprets this map as fully initialized, meaning that all its values
279    /// have been given a concrete value.
280    ///
281    /// # Safety
282    /// The caller must ensure that the map has actually been fully
283    /// initialized, meaning that all values have been given an initialized
284    /// value such that calling `MaybeUninit::assume_init` on them would be
285    /// safe.
286    pub const unsafe fn assume_init(self) -> Assoc<K, V> {
287        Assoc {
288            // SAFETY: the caller guarantees that all elements of
289            // `self.storage` have been initialized.
290            storage: unsafe { assume_init_array(self.storage) },
291        }
292    }
293}
294
295/// Describes a key type for [Assoc].
296///
297/// # Safety
298/// Whenever `Storage<V>` is an array `[V; N]` for some N, `Self` must be less
299/// than `N` when converted to `usize` via `key_impl_to_index`.
300unsafe trait KeyImpl: Copy + TransmuteSafe<Self::Repr> {
301    type Storage<V>;
302    type Repr: Copy + ConstIntoUSize;
303}
304
305#[doc(hidden)]
306#[inline(always)]
307pub const fn key_to_index<K: Key>(key: K) -> usize {
308    let key_impl = transmute_safe(key);
309    key_impl_to_index(key_impl)
310}
311
312#[inline(always)]
313const fn key_impl_to_index<K: KeyImpl>(key: K) -> usize {
314    let repr: K::Repr = transmute_safe(key);
315    into_usize(repr)
316}
317
318/// Indirectly defines a way to use `Self` as a key for [Assoc].
319trait Key: TransmuteSafe<Self::Impl> {
320    type Impl: KeyImpl;
321}
322
323#[repr(transparent)]
324#[derive_where(Copy, Clone)]
325struct EnumKeyImpl<T: PrimitiveEnum, _U>(T, PhantomData<_U>);
326
327// SAFETY: `EnumKey<T>` has the same representation as `T`.
328unsafe impl<T: PrimitiveEnum, _U> TransmuteSafe<EnumKeyImpl<T, _U>> for T {}
329
330// SAFETY: `EnumKey<T>` has the same representation as `T`, while `T` has the
331// same representation as `<T::Layout as EnumLayoutTrait>::Discriminant`, since
332// `PrimitiveEnum` implies `TransmuteSafe<<T::Layout as EnumLayoutTrait>::Discriminant>`.
333unsafe impl<T: PrimitiveEnum, _U>
334    TransmuteSafe<<T::Layout as PrimitiveEnumLayoutTrait>::Discriminant> for EnumKeyImpl<T, _U>
335{
336}
337
338impl<T: PrimitiveEnum> Key for T
339where
340    EnumKeyImpl<T, <<T as PrimitiveEnum>::Layout as PrimitiveEnumLayoutTrait>::MaxVariants>:
341        KeyImpl,
342{
343    type Impl = EnumKeyImpl<T, <T::Layout as PrimitiveEnumLayoutTrait>::MaxVariants>;
344}
345
346/// Indicates that `Self` is a primitive enum type, meaning that it is an enum
347/// with a `#[repr(primitive_type)]` attribute.
348///
349/// # Safety
350/// The implementors must ensure that `Layout` exactly describes `Self`.
351pub unsafe trait PrimitiveEnum: Copy {
352    /// The layout of `Self`.
353    type Layout: PrimitiveEnumLayoutTrait;
354}
355
356// SAFETY: The invariant of `PrimitiveEnum` implies that `Self` always
357// represents a valid enum discriminant when converted to usize, so it must be
358// a non-negative integer that is less than `MAX_VARIANTS`.
359unsafe impl<T: PrimitiveEnum, const MAX_VARIANTS: usize> KeyImpl
360    for EnumKeyImpl<T, ConstUSize<MAX_VARIANTS>>
361where
362    <<T as PrimitiveEnum>::Layout as PrimitiveEnumLayoutTrait>::MaxVariants:
363        Is<ConstUSize<MAX_VARIANTS>>,
364    EnumKeyImpl<T, ConstUSize<MAX_VARIANTS>>:
365        TransmuteSafe<<<T as PrimitiveEnum>::Layout as PrimitiveEnumLayoutTrait>::Discriminant>,
366{
367    type Storage<V> = [V; MAX_VARIANTS];
368    type Repr = <<T as PrimitiveEnum>::Layout as PrimitiveEnumLayoutTrait>::Discriminant;
369}
370
371// See `PrimitiveEnumLayout`.
372trait PrimitiveEnumLayoutTrait {
373    type Discriminant: Copy + ConstIntoUSize;
374    type MaxVariants: IsConstUSize;
375}
376
377/// Describes the layout of an enum with a `#[repr(primitive_type)]` attribute.
378///
379/// # Parameters
380/// * `Discriminant` - The underlying numerical type used to represent enum variants.
381/// * `MAX_MAX_VARIANTS` - The maximum number of variants this enum can have, equal to
382///   the greatest discriminant value among the enum's variants plus 1.
383pub struct PrimitiveEnumLayout<Discriminant, const MAX_MAX_VARIANTS: usize> {
384    _marker: PhantomData<Discriminant>,
385}
386
387impl<Discriminant, const MAX_VARIANTS: usize> PrimitiveEnumLayoutTrait
388    for PrimitiveEnumLayout<Discriminant, MAX_VARIANTS>
389where
390    Discriminant: Copy + ConstIntoUSize,
391{
392    type Discriminant = Discriminant;
393    type MaxVariants = ConstUSize<MAX_VARIANTS>;
394}