1use {
10 bytemuck::{Pod, Zeroable},
11 solana_program_error::ProgramError,
12 solana_program_option::COption,
13 solana_pubkey::{Pubkey, PUBKEY_BYTES},
14};
15
16pub trait Nullable: PartialEq + Pod + Sized {
21 const NONE: Self;
23
24 fn is_none(&self) -> bool {
26 self == &Self::NONE
27 }
28
29 fn is_some(&self) -> bool {
31 !self.is_none()
32 }
33}
34
35#[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 #[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 #[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 #[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
82unsafe impl<T: Nullable> Pod for PodOption<T> {}
87
88unsafe 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
124impl 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}