tensor_toolbox/
nullable.rs

1use anchor_lang::prelude::*;
2
3const DEFAULT_PUBKEY: Pubkey = Pubkey::new_from_array([0u8; 32]);
4
5/// Used for Borsh types that can have a `None` value.
6pub trait Nullable: AnchorSerialize + AnchorDeserialize + PartialEq {
7    /// The value that represents `None`.
8    const NONE: Self;
9
10    /// Indicates if the value is `Some`.
11    fn is_some(&self) -> bool {
12        *self != Self::NONE
13    }
14
15    /// Indicates if the value is `None`.
16    fn is_none(&self) -> bool {
17        *self == Self::NONE
18    }
19}
20
21/// Borsh encodes standard `Option`s with either a 1 or 0 representing the `Some` or `None variants.
22/// This means an `Option<Pubkey>` for example, is alternately encoded as 33 bytes or 1 byte.
23/// `NullableOption` type allows creating a fixed-size generic `Option` type that can be used as an `Option<T>` without
24/// requiring extra space to indicate if the value is `Some` or `None`. In the `Pubkey` example it is now
25/// always 32 bytes making it friendly to getProgramAccount calls and creating fixed-size on-chain accounts.
26///
27/// This requires `T` to implement the `Nullable` trait so that it defines a `NONE` value and can indicate if it is `Some` or `None`.
28#[repr(C)]
29#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, Eq, PartialEq, Hash)]
30pub struct NullableOption<T: Nullable>(T);
31
32impl<T: Nullable> NullableOption<T> {
33    #[inline]
34    pub fn new(value: T) -> Self {
35        Self(value)
36    }
37
38    #[inline]
39    pub fn value(&self) -> Option<&T> {
40        if self.0.is_some() {
41            Some(&self.0)
42        } else {
43            None
44        }
45    }
46
47    #[inline]
48    pub fn value_mut(&mut self) -> Option<&mut T> {
49        if self.0.is_some() {
50            Some(&mut self.0)
51        } else {
52            None
53        }
54    }
55
56    #[inline]
57    pub fn none() -> Self {
58        Self(T::NONE)
59    }
60}
61
62impl<T: Nullable> From<Option<T>> for NullableOption<T> {
63    fn from(option: Option<T>) -> Self {
64        match option {
65            Some(value) => Self::new(value),
66            None => Self::none(),
67        }
68    }
69}
70
71impl<T: Nullable> Default for NullableOption<T> {
72    fn default() -> Self {
73        Self::none()
74    }
75}
76
77impl Nullable for Pubkey {
78    const NONE: Self = DEFAULT_PUBKEY;
79}
80
81macro_rules! impl_nullable_for_ux {
82    ($ux:ty) => {
83        impl Nullable for $ux {
84            const NONE: Self = 0;
85        }
86    };
87}
88
89impl_nullable_for_ux!(u8);
90impl_nullable_for_ux!(u16);
91impl_nullable_for_ux!(u32);
92impl_nullable_for_ux!(u64);
93impl_nullable_for_ux!(u128);
94impl_nullable_for_ux!(usize);
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_nullable_option() {
102        let mut none = NullableOption::<u8>::none();
103        assert!(none.value().is_none());
104        assert!(none.value_mut().is_none());
105
106        let mut some = NullableOption::new(42u8);
107        assert_eq!(some.value().unwrap(), &42);
108        assert_eq!(some.value_mut().unwrap(), &mut 42);
109
110        let opt = NullableOption::<Pubkey>::new(DEFAULT_PUBKEY);
111        assert!(opt.value().is_none());
112
113        let opt = NullableOption::<Pubkey>::new(Pubkey::new_from_array([1u8; 32]));
114        assert!(opt.value().is_some());
115        assert_eq!(opt.value().unwrap(), &Pubkey::new_from_array([1u8; 32]));
116    }
117
118    #[test]
119    fn test_nullable_pubkey() {
120        let none = Pubkey::NONE;
121        assert!(none.is_none());
122        assert!(!none.is_some());
123
124        let some = Pubkey::new_from_array([1u8; 32]);
125        assert!(!some.is_none());
126        assert!(some.is_some());
127    }
128
129    #[test]
130    fn test_nullable_ux() {
131        let none = 0u8;
132        assert!(none.is_none());
133        assert!(!none.is_some());
134
135        let some = 42u8;
136        assert!(!some.is_none());
137        assert!(some.is_some());
138    }
139}