Skip to main content

cranelift_entity/
packed_option.rs

1//! Compact representation of `Option<T>` for types with a reserved value.
2//!
3//! Small Cranelift types like the 32-bit entity references are often used in tables and linked
4//! lists where an `Option<T>` is needed. Unfortunately, that would double the size of the tables
5//! because `Option<T>` is twice as big as `T`.
6//!
7//! This module provides a `PackedOption<T>` for types that have a reserved value that can be used
8//! to represent `None`.
9
10use core::{fmt, mem};
11use wasmtime_core::{alloc::TryClone, error::OutOfMemory};
12
13#[cfg(feature = "enable-serde")]
14use serde_derive::{Deserialize, Serialize};
15
16/// Types that have a reserved value which can't be created any other way.
17pub trait ReservedValue {
18    /// Create an instance of the reserved value.
19    fn reserved_value() -> Self;
20    /// Checks whether value is the reserved one.
21    fn is_reserved_value(&self) -> bool;
22}
23
24/// Packed representation of `Option<T>`.
25///
26/// This is a wrapper around a `T`, using `T::reserved_value` to represent
27/// `None`.
28#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
29#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
30#[repr(transparent)]
31pub struct PackedOption<T: ReservedValue>(T);
32
33impl<T> TryClone for PackedOption<T>
34where
35    T: ReservedValue + TryClone,
36{
37    fn try_clone(&self) -> Result<Self, OutOfMemory> {
38        Ok(Self(self.0.try_clone()?))
39    }
40}
41
42impl<T: ReservedValue> PackedOption<T> {
43    /// Const constructor wrapping a raw `T`.
44    ///
45    /// To create `None`, pass `T::reserved_value()`. To create `Some(val)`,
46    /// pass a non-reserved `val`.
47    pub const fn new(val: T) -> Self {
48        Self(val)
49    }
50
51    /// Returns `true` if the packed option is a `None` value.
52    pub fn is_none(&self) -> bool {
53        self.0.is_reserved_value()
54    }
55
56    /// Returns `true` if the packed option is a `Some` value.
57    pub fn is_some(&self) -> bool {
58        !self.0.is_reserved_value()
59    }
60
61    /// Expand the packed option into a normal `Option`.
62    pub fn expand(self) -> Option<T> {
63        if self.is_none() { None } else { Some(self.0) }
64    }
65
66    /// Maps a `PackedOption<T>` to `Option<U>` by applying a function to a contained value.
67    pub fn map<U, F>(self, f: F) -> Option<U>
68    where
69        F: FnOnce(T) -> U,
70    {
71        self.expand().map(f)
72    }
73
74    /// Unwrap a packed `Some` value or panic.
75    #[track_caller]
76    pub fn unwrap(self) -> T {
77        self.expand().unwrap()
78    }
79
80    /// Unwrap a packed `Some` value or panic.
81    #[track_caller]
82    pub fn expect(self, msg: &str) -> T {
83        self.expand().expect(msg)
84    }
85
86    /// Takes the value out of the packed option, leaving a `None` in its place.
87    pub fn take(&mut self) -> Option<T> {
88        mem::replace(self, None.into()).expand()
89    }
90}
91
92impl<T: ReservedValue> Default for PackedOption<T> {
93    /// Create a default packed option representing `None`.
94    fn default() -> Self {
95        Self(T::reserved_value())
96    }
97}
98
99impl<T: ReservedValue> From<T> for PackedOption<T> {
100    /// Convert `t` into a packed `Some(x)`.
101    fn from(t: T) -> Self {
102        debug_assert!(
103            !t.is_reserved_value(),
104            "Can't make a PackedOption from the reserved value."
105        );
106        Self(t)
107    }
108}
109
110impl<T: ReservedValue> From<Option<T>> for PackedOption<T> {
111    /// Convert an option into its packed equivalent.
112    fn from(opt: Option<T>) -> Self {
113        match opt {
114            None => Self::default(),
115            Some(t) => t.into(),
116        }
117    }
118}
119
120impl<T: ReservedValue> From<PackedOption<T>> for Option<T> {
121    fn from(packed: PackedOption<T>) -> Option<T> {
122        packed.expand()
123    }
124}
125
126impl<T> fmt::Debug for PackedOption<T>
127where
128    T: ReservedValue + fmt::Debug,
129{
130    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131        if self.is_none() {
132            write!(f, "None")
133        } else {
134            write!(f, "Some({:?})", self.0)
135        }
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    // Dummy entity class, with no Copy or Clone.
144    #[derive(Debug, PartialEq, Eq)]
145    struct NoC(u32);
146
147    impl ReservedValue for NoC {
148        fn reserved_value() -> Self {
149            NoC(13)
150        }
151
152        fn is_reserved_value(&self) -> bool {
153            self.0 == 13
154        }
155    }
156
157    #[test]
158    fn moves() {
159        let x = NoC(3);
160        let somex: PackedOption<NoC> = x.into();
161        assert!(!somex.is_none());
162        assert_eq!(somex.expand(), Some(NoC(3)));
163
164        let none: PackedOption<NoC> = None.into();
165        assert!(none.is_none());
166        assert_eq!(none.expand(), None);
167    }
168
169    // Dummy entity class, with Copy.
170    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
171    struct Ent(u32);
172
173    impl ReservedValue for Ent {
174        fn reserved_value() -> Self {
175            Ent(13)
176        }
177
178        fn is_reserved_value(&self) -> bool {
179            self.0 == 13
180        }
181    }
182
183    #[test]
184    fn copies() {
185        let x = Ent(2);
186        let some: PackedOption<Ent> = x.into();
187        assert_eq!(some.expand(), x.into());
188        assert_eq!(some, x.into());
189    }
190}