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}