stdext/
option.rs

1//! Extension traits for `std::Option`.
2
3/// Extension trait with useful methods for [`std::option::Option`].
4///
5/// [`std::time::Option`]: https://doc.rust-lang.org/std/option/enum.Option.html
6pub trait OptionExt<T> {
7    /// Combines `self` and another `Option`.
8    ///
9    /// If `self` is `Some(s)` and `other` is `Some(o)`, this method returns `Some((s, o))`.
10    /// Otherwise, `None` is returned.
11    ///
12    /// **Note:** `std::Option` already provides a [`zip`] method which serves exact same purpose,
13    /// but currently it's unstable. Once it's stabilized, this method will be marked as deprecated
14    /// with a prompt to use the stanard method instead.
15    ///
16    /// # Examples
17    ///
18    /// ```
19    /// use stdext::prelude::*;
20    ///
21    /// let x = Some(1);
22    /// let y = Some("hi");
23    /// let z = None::<u8>;
24    ///
25    /// assert_eq!(x.combine(y), Some((1, "hi")));
26    /// assert_eq!(x.combine(z), None);
27    /// ```
28    ///
29    /// [`zip`]: https://doc.rust-lang.org/std/option/enum.Option.html#method.zip
30    fn combine<U>(self, other: Option<U>) -> Option<(T, U)>;
31
32    /// Combines `self` and another `Option` with function `f`.
33    ///
34    /// If `self` is `Some(s)` and `other` is `Some(o)`, this method returns `Some(f(s, o))`.
35    /// Otherwise, `None` is returned.
36    ///
37    /// **Note:** `std::Option` already provides a [`zip_with`] method which serves exact same purpose,
38    /// but currently it's unstable. Once it's stabilized, this method will be marked as deprecated
39    /// with a prompt to use the stanard method instead.
40    ///
41    /// # Examples
42    ///
43    /// ```
44    /// use stdext::prelude::*;
45    ///
46    /// #[derive(Debug, PartialEq)]
47    /// struct Point {
48    ///     x: f64,
49    ///     y: f64,
50    /// }
51    ///
52    /// impl Point {
53    ///     fn new(x: f64, y: f64) -> Self {
54    ///         Self { x, y }
55    ///     }
56    /// }
57    ///
58    /// let x = Some(17.5);
59    /// let y = Some(42.7);
60    ///
61    /// assert_eq!(x.combine_with(y, Point::new), Some(Point { x: 17.5, y: 42.7 }));
62    /// assert_eq!(x.combine_with(None, Point::new), None);
63    /// ```
64    ///
65    /// [`zip_with`]: https://doc.rust-lang.org/std/option/enum.Option.html#method.zip_with
66    fn combine_with<U, F, R>(self, other: Option<U>, f: F) -> Option<R>
67    where
68        F: FnOnce(T, U) -> R;
69}
70
71impl<T> OptionExt<T> for Option<T> {
72    fn combine<U>(self, other: Option<U>) -> Option<(T, U)> {
73        match (self, other) {
74            (Some(left), Some(right)) => Some((left, right)),
75            _ => None,
76        }
77    }
78
79    fn combine_with<U, F, R>(self, other: Option<U>, f: F) -> Option<R>
80    where
81        F: FnOnce(T, U) -> R,
82    {
83        self.combine(other).map(|(l, r)| f(l, r))
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn combine() {
93        // Test vector of (left, right, expected) values.
94        let test_vector = vec![
95            (Some(1), Some(2), Some((1, 2))),
96            (Some(1), None, None),
97            (None, Some(2), None),
98            (None, None, None),
99        ];
100
101        for (left, right, expected) in test_vector {
102            assert_eq!(left.combine(right), expected);
103        }
104    }
105
106    #[test]
107    fn combine_with() {
108        fn f(l: i32, r: i32) -> i32 {
109            l + r
110        }
111
112        // Test vector of (left, right, expected) values.
113        let test_vector = vec![
114            (Some(1), Some(2), Some(3)),
115            (Some(1), None, None),
116            (None, Some(2), None),
117            (None, None, None),
118        ];
119
120        for (left, right, expected) in test_vector {
121            assert_eq!(left.combine_with(right, f), expected);
122        }
123    }
124}