spl_pod/
option.rs

1//! Generic `Option` that can be used as a `Pod` for types that can have
2//! a designated `None` value.
3//!
4//! For example, a 64-bit unsigned integer can designate `0` as a `None` value.
5//! This would be equivalent to
6//! [`Option<NonZeroU64>`](https://doc.rust-lang.org/std/num/type.NonZeroU64.html)
7//! and provide the same memory layout optimization.
8
9use {
10    bytemuck::{Pod, Zeroable},
11    solana_program_error::ProgramError,
12    solana_program_option::COption,
13    solana_pubkey::{Pubkey, PUBKEY_BYTES},
14};
15
16/// Trait for types that can be `None`.
17///
18/// This trait is used to indicate that a type can be `None` according to a
19/// specific value.
20pub trait Nullable: PartialEq + Pod + Sized {
21    /// Value that represents `None` for the type.
22    const NONE: Self;
23
24    /// Indicates whether the value is `None` or not.
25    fn is_none(&self) -> bool {
26        self == &Self::NONE
27    }
28
29    /// Indicates whether the value is `Some`` value of type `T`` or not.
30    fn is_some(&self) -> bool {
31        !self.is_none()
32    }
33}
34
35/// A "pod-enabled" type that can be used as an `Option<T>` without
36/// requiring extra space to indicate if the value is `Some` or `None`.
37///
38/// This can be used when a specific value of `T` indicates that its
39/// value is `None`.
40#[repr(transparent)]
41#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
42pub struct PodOption<T: Nullable>(T);
43
44impl<T: Nullable> Default for PodOption<T> {
45    fn default() -> Self {
46        Self(T::NONE)
47    }
48}
49
50impl<T: Nullable> PodOption<T> {
51    /// Returns the contained value as an `Option`.
52    #[inline]
53    pub fn get(self) -> Option<T> {
54        if self.0.is_none() {
55            None
56        } else {
57            Some(self.0)
58        }
59    }
60
61    /// Returns the contained value as an `Option`.
62    #[inline]
63    pub fn as_ref(&self) -> Option<&T> {
64        if self.0.is_none() {
65            None
66        } else {
67            Some(&self.0)
68        }
69    }
70
71    /// Returns the contained value as a mutable `Option`.
72    #[inline]
73    pub fn as_mut(&mut self) -> Option<&mut T> {
74        if self.0.is_none() {
75            None
76        } else {
77            Some(&mut self.0)
78        }
79    }
80}
81
82/// ## Safety
83///
84/// `PodOption` is a transparent wrapper around a `Pod` type `T` with identical
85/// data representation.
86unsafe impl<T: Nullable> Pod for PodOption<T> {}
87
88/// ## Safety
89///
90/// `PodOption` is a transparent wrapper around a `Pod` type `T` with identical
91/// data representation.
92unsafe impl<T: Nullable> Zeroable for PodOption<T> {}
93
94impl<T: Nullable> From<T> for PodOption<T> {
95    fn from(value: T) -> Self {
96        PodOption(value)
97    }
98}
99
100impl<T: Nullable> TryFrom<Option<T>> for PodOption<T> {
101    type Error = ProgramError;
102
103    fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
104        match value {
105            Some(value) if value.is_none() => Err(ProgramError::InvalidArgument),
106            Some(value) => Ok(PodOption(value)),
107            None => Ok(PodOption(T::NONE)),
108        }
109    }
110}
111
112impl<T: Nullable> TryFrom<COption<T>> for PodOption<T> {
113    type Error = ProgramError;
114
115    fn try_from(value: COption<T>) -> Result<Self, Self::Error> {
116        match value {
117            COption::Some(value) if value.is_none() => Err(ProgramError::InvalidArgument),
118            COption::Some(value) => Ok(PodOption(value)),
119            COption::None => Ok(PodOption(T::NONE)),
120        }
121    }
122}
123
124/// Implementation of `Nullable` for `Pubkey`.
125impl Nullable for Pubkey {
126    const NONE: Self = Pubkey::new_from_array([0u8; PUBKEY_BYTES]);
127}
128
129#[cfg(test)]
130mod tests {
131    use {super::*, crate::bytemuck::pod_slice_from_bytes};
132    const ID: Pubkey = Pubkey::from_str_const("TestSysvar111111111111111111111111111111111");
133
134    #[test]
135    fn test_pod_option_pubkey() {
136        let some_pubkey = PodOption::from(ID);
137        assert_eq!(some_pubkey.get(), Some(ID));
138
139        let none_pubkey = PodOption::from(Pubkey::default());
140        assert_eq!(none_pubkey.get(), None);
141
142        let mut data = Vec::with_capacity(64);
143        data.extend_from_slice(ID.as_ref());
144        data.extend_from_slice(&[0u8; 32]);
145
146        let values = pod_slice_from_bytes::<PodOption<Pubkey>>(&data).unwrap();
147        assert_eq!(values[0], PodOption::from(ID));
148        assert_eq!(values[1], PodOption::from(Pubkey::default()));
149
150        let option_pubkey = Some(ID);
151        let pod_option_pubkey: PodOption<Pubkey> = option_pubkey.try_into().unwrap();
152        assert_eq!(pod_option_pubkey, PodOption::from(ID));
153        assert_eq!(
154            pod_option_pubkey,
155            PodOption::try_from(option_pubkey).unwrap()
156        );
157
158        let coption_pubkey = COption::Some(ID);
159        let pod_option_pubkey: PodOption<Pubkey> = coption_pubkey.try_into().unwrap();
160        assert_eq!(pod_option_pubkey, PodOption::from(ID));
161        assert_eq!(
162            pod_option_pubkey,
163            PodOption::try_from(coption_pubkey).unwrap()
164        );
165    }
166
167    #[test]
168    fn test_try_from_option() {
169        let some_pubkey = Some(ID);
170        assert_eq!(PodOption::try_from(some_pubkey).unwrap(), PodOption(ID));
171
172        let none_pubkey = None;
173        assert_eq!(
174            PodOption::try_from(none_pubkey).unwrap(),
175            PodOption::from(Pubkey::NONE)
176        );
177
178        let invalid_option = Some(Pubkey::NONE);
179        let err = PodOption::try_from(invalid_option).unwrap_err();
180        assert_eq!(err, ProgramError::InvalidArgument);
181    }
182
183    #[test]
184    fn test_default() {
185        let def = PodOption::<Pubkey>::default();
186        assert_eq!(def, None.try_into().unwrap());
187    }
188}