Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit c1e3763

Browse files
authored
[libraries/pod]: improve PodOption type (#7076)
* Improve PodOption type * Use associated constant * Add exhaustive matching
1 parent 8e09659 commit c1e3763

File tree

1 file changed

+66
-23
lines changed

1 file changed

+66
-23
lines changed

libraries/pod/src/option.rs

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,25 @@
88
99
use {
1010
bytemuck::{Pod, Zeroable},
11-
solana_program::{program_option::COption, pubkey::Pubkey},
11+
solana_program::{
12+
program_error::ProgramError,
13+
program_option::COption,
14+
pubkey::{Pubkey, PUBKEY_BYTES},
15+
},
1216
};
1317

1418
/// Trait for types that can be `None`.
1519
///
1620
/// This trait is used to indicate that a type can be `None` according to a
1721
/// specific value.
18-
pub trait Nullable: Default + Pod {
22+
pub trait Nullable: PartialEq + Pod + Sized {
23+
/// Value that represents `None` for the type.
24+
const NONE: Self;
25+
1926
/// Indicates whether the value is `None` or not.
20-
fn is_none(&self) -> bool;
27+
fn is_none(&self) -> bool {
28+
self == &Self::NONE
29+
}
2130

2231
/// Indicates whether the value is `Some`` value of type `T`` or not.
2332
fn is_some(&self) -> bool {
@@ -66,8 +75,16 @@ impl<T: Nullable> PodOption<T> {
6675
}
6776
}
6877

78+
/// ## Safety
79+
///
80+
/// `PodOption` is a transparent wrapper around a `Pod` type `T` with identical
81+
/// data representation.
6982
unsafe impl<T: Nullable> Pod for PodOption<T> {}
7083

84+
/// ## Safety
85+
///
86+
/// `PodOption` is a transparent wrapper around a `Pod` type `T` with identical
87+
/// data representation.
7188
unsafe impl<T: Nullable> Zeroable for PodOption<T> {}
7289

7390
impl<T: Nullable> From<T> for PodOption<T> {
@@ -76,32 +93,33 @@ impl<T: Nullable> From<T> for PodOption<T> {
7693
}
7794
}
7895

79-
impl<T: Nullable> From<Option<T>> for PodOption<T> {
80-
fn from(from: Option<T>) -> Self {
81-
match from {
82-
Some(value) => PodOption(value),
83-
None => PodOption(T::default()),
96+
impl<T: Nullable> TryFrom<Option<T>> for PodOption<T> {
97+
type Error = ProgramError;
98+
99+
fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
100+
match value {
101+
Some(value) if value.is_none() => Err(ProgramError::InvalidArgument),
102+
Some(value) => Ok(PodOption(value)),
103+
None => Ok(PodOption(T::NONE)),
84104
}
85105
}
86106
}
87107

88-
impl<T: Nullable> From<COption<T>> for PodOption<T> {
89-
fn from(from: COption<T>) -> Self {
90-
match from {
91-
COption::Some(value) => PodOption(value),
92-
COption::None => PodOption(T::default()),
108+
impl<T: Nullable> TryFrom<COption<T>> for PodOption<T> {
109+
type Error = ProgramError;
110+
111+
fn try_from(value: COption<T>) -> Result<Self, Self::Error> {
112+
match value {
113+
COption::Some(value) if value.is_none() => Err(ProgramError::InvalidArgument),
114+
COption::Some(value) => Ok(PodOption(value)),
115+
COption::None => Ok(PodOption(T::NONE)),
93116
}
94117
}
95118
}
96119

97120
/// Implementation of `Nullable` for `Pubkey`.
98-
///
99-
/// The implementation assumes that the default value of `Pubkey` represents
100-
/// the `None` value.
101121
impl Nullable for Pubkey {
102-
fn is_none(&self) -> bool {
103-
self == &Pubkey::default()
104-
}
122+
const NONE: Self = Pubkey::new_from_array([0u8; PUBKEY_BYTES]);
105123
}
106124

107125
#[cfg(test)]
@@ -126,13 +144,38 @@ mod tests {
126144
assert_eq!(values[1], PodOption::from(Pubkey::default()));
127145

128146
let option_pubkey = Some(sysvar::ID);
129-
let pod_option_pubkey: PodOption<Pubkey> = option_pubkey.into();
147+
let pod_option_pubkey: PodOption<Pubkey> = option_pubkey.try_into().unwrap();
130148
assert_eq!(pod_option_pubkey, PodOption::from(sysvar::ID));
131-
assert_eq!(pod_option_pubkey, PodOption::from(option_pubkey));
149+
assert_eq!(
150+
pod_option_pubkey,
151+
PodOption::try_from(option_pubkey).unwrap()
152+
);
132153

133154
let coption_pubkey = COption::Some(sysvar::ID);
134-
let pod_option_pubkey: PodOption<Pubkey> = coption_pubkey.into();
155+
let pod_option_pubkey: PodOption<Pubkey> = coption_pubkey.try_into().unwrap();
135156
assert_eq!(pod_option_pubkey, PodOption::from(sysvar::ID));
136-
assert_eq!(pod_option_pubkey, PodOption::from(coption_pubkey));
157+
assert_eq!(
158+
pod_option_pubkey,
159+
PodOption::try_from(coption_pubkey).unwrap()
160+
);
161+
}
162+
163+
#[test]
164+
fn test_try_from_option() {
165+
let some_pubkey = Some(sysvar::ID);
166+
assert_eq!(
167+
PodOption::try_from(some_pubkey).unwrap(),
168+
PodOption(sysvar::ID)
169+
);
170+
171+
let none_pubkey = None;
172+
assert_eq!(
173+
PodOption::try_from(none_pubkey).unwrap(),
174+
PodOption::from(Pubkey::NONE)
175+
);
176+
177+
let invalid_option = Some(Pubkey::NONE);
178+
let err = PodOption::try_from(invalid_option).unwrap_err();
179+
assert_eq!(err, ProgramError::InvalidArgument);
137180
}
138181
}

0 commit comments

Comments
 (0)