option_like/lib.rs
1//! Create your own enum type that behaves like Rust's `Option` but with custom names.
2//!
3//! # Example
4//!
5//! ```
6//! use option_like::option_like;
7//!
8//! option_like!(
9//! #[derive(Debug, PartialEq)]
10//! pub enum Cached<T> {
11//! Hit(T),
12//! Miss,
13//! }
14//!
15//! is_some => is_hit
16//! is_none => is_miss
17//! );
18//!
19//! // Create instances
20//! let c1 = Cached::<u32>::Hit(42);
21//! let c2 = Cached::<u32>::Miss;
22//!
23//! // Boolean tests
24//! assert!(c1.is_hit());
25//! assert!(c2.is_miss());
26//!
27//! // Convert to Option
28//! assert_eq!(Option::<u32>::from(c1), Some(42));
29//! assert_eq!(Option::<u32>::from(c2), None);
30//!
31//! // Convert from Option
32//! assert_eq!(Cached::<u32>::from(Some(42)), Cached::Hit(42));
33//! assert_eq!(Cached::<u32>::from(None), Cached::Miss);
34//! ```
35
36#![no_std]
37
38/// Creates a new enum type that behaves like Rust's `Option<T>` but with custom names.
39///
40/// This macro allows you to create your own Option-like enum with customized names for the variants
41/// and boolean test methods, while providing automatic conversions to and from the standard Option type.
42///
43/// # Parameters
44///
45/// - `$(#[$meta:meta])*`: Optional attributes to apply to the enum (e.g., `#[derive(...)]`)
46/// - `$vis`: Visibility of the enum (e.g., `pub`)
47/// - `$name`: Name of the enum (e.g., `Cached`)
48/// - `$some`: Name of the variant that holds a value (e.g., `Hit`)
49/// - `$none`: Name of the empty variant (e.g., `Miss`)
50/// - `is_some => $is_some`: Name of the method that checks if the enum holds a value (e.g., `is_hit`)
51/// - `is_none => $is_none`: Name of the method that checks if the enum is empty (e.g., `is_miss`)
52#[macro_export]
53macro_rules! option_like {
54 (
55 $(#[$meta:meta])*
56 $vis:vis enum $name:ident<T> {
57 $(#[$some_meta:meta])*
58 $some:ident(T),
59 $(#[$none_meta:meta])*
60 $none:ident,
61 }
62
63 is_some => $is_some:ident
64 is_none => $is_none:ident
65 ) => {
66 $(#[$meta])*
67 $vis enum $name<T> {
68 $(#[$some_meta])*
69 $some(T),
70 $(#[$none_meta])*
71 $none,
72 }
73
74 use $name::*;
75
76 impl<T> $name<T> {
77 pub fn $is_some(&self) -> bool {
78 match self {
79 $some(_) => true,
80 $none => false,
81 }
82 }
83
84 pub fn $is_none(&self) -> bool {
85 match self {
86 $some(_) => false,
87 $none => true,
88 }
89 }
90
91 #[inline]
92 pub fn map<U, F>(self, f: F) -> $name<U>
93 where
94 F: FnOnce(T) -> U,
95 {
96 match self {
97 $some(x) => $some(f(x)),
98 $none => $none,
99 }
100 }
101 }
102
103 impl<T> From<Option<T>> for $name<T> {
104 fn from(value: Option<T>) -> Self {
105 match value {
106 Some(inner) => $some(inner),
107 None => $none
108 }
109 }
110 }
111
112 impl<T> From<$name<T>> for Option<T> {
113 fn from(value: $name<T>) -> Option<T> {
114 match value {
115 $some(inner) => Some(inner),
116 $none => None
117 }
118 }
119 }
120 };
121}
122
123#[cfg(test)]
124mod tests {
125 option_like!(
126 #[derive(Ord, PartialOrd, Eq, PartialEq, Default, Clone, Debug)]
127 enum Cached<T> {
128 Hit(T),
129 #[default]
130 Miss,
131 }
132
133 is_some => is_hit
134 is_none => is_miss
135 );
136
137 const HIT: Cached<bool> = Hit(true);
138 const MISS: Cached<bool> = Miss;
139
140 #[test]
141 fn test_boolean_methods() {
142 assert!(HIT.is_hit());
143 assert!(MISS.is_miss());
144 }
145
146 #[test]
147 fn test_from() {
148 assert_eq!(Option::<bool>::from(HIT.clone()), Some(true));
149 assert_eq!(Option::<bool>::from(MISS.clone()), None);
150 assert_eq!(Cached::<bool>::from(Some(true)), Hit(true));
151 assert_eq!(Cached::<bool>::from(None), Miss);
152 }
153
154 #[test]
155 fn test_map() {
156 assert_eq!(HIT.clone().map(|t| !t), Hit(false));
157 assert_eq!(MISS.clone().map(|t| !t), Miss);
158 }
159}