optcollection/empty_into_none.rs
1use super::is_empty::IsEmpty;
2
3/// A trait to add the `empty_into_none()` method to `Option<T>` (where `T`
4/// implements [`IsEmpty`]) to convert `Option::Some` with an empty [`IsEmpty`]
5/// into `Option::None`.
6///
7/// `IsEmpty` is implemented for the standard collections.
8///
9/// # Examples
10///
11/// `Option::Some` with an empty `Vec` becomes `Option::None`.
12/// ```
13/// use optcollection::EmptyIntoNone;
14///
15/// let some: Option<Vec<&str>> = Some(vec![]);
16/// let none = some.empty_into_none();
17/// assert_eq!(None, none);
18/// ```
19///
20/// `Option::Some` with a non-empty `Vec` remains unchanged.
21/// ```
22/// use optcollection::EmptyIntoNone;
23///
24/// let some = Some(vec!["a", "b", "c"]);
25/// let still_some = some.clone().empty_into_none();
26/// assert_eq!(some, still_some);
27/// ```
28///
29/// `None` remains unchanged.
30/// ```
31/// use optcollection::EmptyIntoNone;
32///
33/// let none: Option<Vec<&str>> = None;
34/// let still_none = none.empty_into_none();
35/// assert_eq!(None, still_none);
36/// ```
37///
38/// Works out of the box with all types from [`std::collections`]:
39///
40/// `BinaryHeap`
41/// ```
42/// use std::collections::BinaryHeap;
43///
44/// use optcollection::EmptyIntoNone;
45///
46/// let some: Option<BinaryHeap<&str>> = Some(BinaryHeap::new());
47/// let none = some.empty_into_none();
48/// assert!(none.is_none());
49/// ```
50///
51/// `BTreeMap`
52/// ```
53/// use std::collections::BTreeMap;
54///
55/// use optcollection::EmptyIntoNone;
56///
57/// let some: Option<BTreeMap<&str, &str>> = Some(BTreeMap::new());
58/// let none = some.empty_into_none();
59/// assert_eq!(None, none);
60/// ```
61///
62/// `BTreeSet`
63/// ```
64/// use std::collections::BTreeSet;
65///
66/// use optcollection::EmptyIntoNone;
67///
68/// let some: Option<BTreeSet<&str>> = Some(BTreeSet::new());
69/// let none = some.empty_into_none();
70/// assert_eq!(None, none);
71/// ```
72///
73/// `HashMap`
74/// ```
75/// use std::collections::HashMap;
76///
77/// use optcollection::EmptyIntoNone;
78///
79/// let some: Option<HashMap<&str, &str>> = Some(HashMap::new());
80/// let none = some.empty_into_none();
81/// assert_eq!(None, none);
82/// ```
83///
84/// `HashSet`
85/// ```
86/// use std::collections::HashSet;
87///
88/// use optcollection::EmptyIntoNone;
89///
90/// let some: Option<HashSet<&str>> = Some(HashSet::new());
91/// let none = some.empty_into_none();
92/// assert_eq!(None, none);
93/// ```
94///
95/// `LinkedList`
96/// ```
97/// use std::collections::LinkedList;
98///
99/// use optcollection::EmptyIntoNone;
100///
101/// let some: Option<LinkedList<&str>> = Some(LinkedList::new());
102/// let none = some.empty_into_none();
103/// assert_eq!(None, none);
104/// ```
105///
106/// `VecDeque`
107/// ```
108/// use std::collections::VecDeque;
109///
110/// use optcollection::EmptyIntoNone;
111///
112/// let some: Option<VecDeque<&str>> = Some(VecDeque::new());
113/// let none = some.empty_into_none();
114/// assert_eq!(None, none);
115/// ```
116///
117/// ## Other Types
118/// Also works for other types that can be empty.
119///
120/// `String`:
121/// ```
122/// use optcollection::EmptyIntoNone;
123///
124/// let some: Option<String> = Some(String::new());
125/// let none = some.empty_into_none();
126/// assert_eq!(None, none);
127/// ```
128///
129/// `&str`:
130/// ```
131/// use optcollection::EmptyIntoNone;
132///
133/// let some: Option<&str> = Some("");
134/// let none = some.empty_into_none();
135/// assert_eq!(None, none);
136/// ```
137///
138/// `&[T]` (or `slice`):
139/// ```
140/// use optcollection::EmptyIntoNone;
141///
142/// let some: Option<&[u8]> = Some("".as_bytes());
143/// let none = some.empty_into_none();
144/// assert_eq!(None, none);
145/// ```
146///
147/// [IsEmpty]: crate::is_empty::IsEmpty
148/// [std::collections]: https://doc.rust-lang.org/std/collections/
149pub trait EmptyIntoNone {
150 /// If the value is `Option::Some` with an empty collection, returns
151 /// `Option::None`. Otherwise the original value is returned.
152 fn empty_into_none(self) -> Self;
153}
154
155impl<T> EmptyIntoNone for Option<T>
156where
157 T: IsEmpty,
158{
159 fn empty_into_none(self) -> Self {
160 self.and_then(|col| (!col.is_empty()).then(|| col))
161 }
162}
163
164#[cfg(test)]
165mod test {
166 use std::collections::{
167 BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque,
168 };
169
170 use maplit::*;
171
172 use super::EmptyIntoNone;
173
174 fn check<C>(col: Option<C>, should_be_some: bool)
175 where
176 C: crate::is_empty::IsEmpty + std::fmt::Debug + Clone,
177 {
178 let into_none = col.clone().empty_into_none();
179 if should_be_some {
180 assert!(
181 into_none.is_some(),
182 "Should be Some: {:?}.empty_into_none()\n\
183 Got: {:?}",
184 col,
185 into_none,
186 );
187 } else {
188 assert!(
189 into_none.is_none(),
190 "Should be None: {:?}.empty_into_none()\n\
191 Got: {:?}",
192 col,
193 into_none,
194 );
195 }
196 }
197
198 #[test]
199 fn binary_heap() {
200 let mut bh = BinaryHeap::<&str>::new();
201 bh.push("a");
202 check(Some(bh.clone()), true);
203
204 bh.push("b");
205 bh.push("c");
206 check(Some(bh), true);
207 check(Some(BinaryHeap::<&str>::new()), false);
208 check(Option::<BinaryHeap<&str>>::None, false);
209 }
210
211 #[test]
212 fn btree_map() {
213 check(Some(btreemap! {"a" => 1}), true);
214 check(Some(btreemap! {"a" => 1, "b" => 2, "c" => 3}), true);
215 check(Some(BTreeMap::<&str, i8>::new()), false);
216 check(Option::<BTreeMap<&str, i8>>::None, false);
217 }
218
219 #[test]
220 fn btree_set() {
221 check(Some(btreeset! {"a"}), true);
222 check(Some(btreeset! {"a", "b", "c"}), true);
223 check(Some(BTreeSet::<&str>::new()), false);
224 check(Option::<BTreeSet<&str>>::None, false);
225 }
226
227 #[test]
228 fn hash_map() {
229 check(Some(hashmap! {"a" => 1}), true);
230 check(Some(hashmap! {"a" => 1, "b" => 2, "c" => 3}), true);
231 check(Some(HashMap::<&str, i8>::new()), false);
232 check(Option::<HashMap<&str, i8>>::None, false);
233 }
234
235 #[test]
236 fn hash_set() {
237 check(Some(hashset! {"a"}), true);
238 check(Some(hashset! {"a", "b", "c"}), true);
239 check(Some(HashSet::<&str>::new()), false);
240 check(Option::<HashSet<&str>>::None, false);
241 }
242
243 #[test]
244 fn linked_list() {
245 let mut ll = LinkedList::new();
246 ll.push_back("a");
247 check(Some(ll.clone()), true);
248
249 ll.push_back("b");
250 ll.push_back("c");
251 check(Some(ll), true);
252 check(Some(LinkedList::<&str>::new()), false);
253 check(Option::<LinkedList<&str>>::None, false);
254 }
255
256 #[test]
257 fn vec() {
258 check(Some(vec!["a"]), true);
259 check(Some(vec!["a", "b", "c"]), true);
260 check(Some(Vec::<&str>::new()), false);
261 check(Option::<Vec<&str>>::None, false);
262 }
263
264 #[test]
265 fn vec_deque() {
266 check(Some(VecDeque::from(vec!["a"])), true);
267 check(Some(VecDeque::from(vec!["a", "b", "c"])), true);
268 check(Some(VecDeque::<&str>::new()), false);
269 check(Option::<VecDeque<&str>>::None, false);
270 }
271
272 #[test]
273 fn string() {
274 check(Some(String::from("a")), true);
275 check(Some(String::new()), false);
276 check(Option::<String>::None, false);
277 }
278
279 #[test]
280 fn str() {
281 check(Some("a"), true);
282 check(Some(""), false);
283 check(Option::<String>::None, false);
284 }
285
286 #[test]
287 fn slice() {
288 check(Some("a".as_bytes()), true);
289 check(Some("".as_bytes()), false);
290 check(Option::<&[u8]>::None, false);
291 }
292}