Skip to main content

scirs2_stats/
either.rs

1//! Crate-local `Either` sum type.
2//!
3//! This module replaces the external `either` crate (pure dependency
4//! reduction). The public path `scirs2_stats::Either` is preserved via a
5//! re-export in `lib.rs`, but the type is no longer interoperable with
6//! `either::Either` from crates.io — it is an independent, crate-owned enum.
7
8/// A value that is one of two possible variants: [`Either::Left`] or
9/// [`Either::Right`].
10///
11/// This is a minimal, crate-local replacement for the `either` crate's
12/// `Either` type (the external dependency was removed for dependency
13/// reduction). Only the surface actually needed by `scirs2-stats` is
14/// provided: construction, pattern matching, and a small set of
15/// ergonomic helpers.
16///
17/// # Examples
18///
19/// ```
20/// use scirs2_stats::Either;
21///
22/// let l: Either<i32, &str> = Either::Left(7);
23/// assert!(l.is_left());
24/// assert_eq!(l.left(), Some(7));
25///
26/// let r: Either<i32, &str> = Either::Right("stats");
27/// assert!(r.is_right());
28/// assert_eq!(r.right(), Some("stats"));
29/// ```
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum Either<L, R> {
32    /// The left variant.
33    Left(L),
34    /// The right variant.
35    Right(R),
36}
37
38impl<L, R> Either<L, R> {
39    /// Returns `true` if this is the [`Either::Left`] variant.
40    pub fn is_left(&self) -> bool {
41        matches!(self, Either::Left(_))
42    }
43
44    /// Returns `true` if this is the [`Either::Right`] variant.
45    pub fn is_right(&self) -> bool {
46        matches!(self, Either::Right(_))
47    }
48
49    /// Consumes the value, returning the left value if present.
50    pub fn left(self) -> Option<L> {
51        match self {
52            Either::Left(l) => Some(l),
53            Either::Right(_) => None,
54        }
55    }
56
57    /// Consumes the value, returning the right value if present.
58    pub fn right(self) -> Option<R> {
59        match self {
60            Either::Left(_) => None,
61            Either::Right(r) => Some(r),
62        }
63    }
64
65    /// Maps the left value with `f`, leaving a right value untouched.
66    pub fn map_left<T, F: FnOnce(L) -> T>(self, f: F) -> Either<T, R> {
67        match self {
68            Either::Left(l) => Either::Left(f(l)),
69            Either::Right(r) => Either::Right(r),
70        }
71    }
72
73    /// Maps the right value with `f`, leaving a left value untouched.
74    pub fn map_right<T, F: FnOnce(R) -> T>(self, f: F) -> Either<L, T> {
75        match self {
76            Either::Left(l) => Either::Left(l),
77            Either::Right(r) => Either::Right(f(r)),
78        }
79    }
80
81    /// Converts `&Either<L, R>` to `Either<&L, &R>`.
82    pub fn as_ref(&self) -> Either<&L, &R> {
83        match self {
84            Either::Left(l) => Either::Left(l),
85            Either::Right(r) => Either::Right(r),
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::Either;
93
94    #[test]
95    fn construction_and_match() {
96        let l: Either<i32, f64> = Either::Left(3);
97        let r: Either<i32, f64> = Either::Right(2.5);
98
99        match l {
100            Either::Left(v) => assert_eq!(v, 3),
101            Either::Right(_) => panic!("expected Left"),
102        }
103        match r {
104            Either::Left(_) => panic!("expected Right"),
105            Either::Right(v) => assert!((v - 2.5).abs() < f64::EPSILON),
106        }
107    }
108
109    #[test]
110    fn predicates_and_extractors() {
111        let l: Either<i32, &str> = Either::Left(1);
112        let r: Either<i32, &str> = Either::Right("x");
113
114        assert!(l.is_left());
115        assert!(!l.is_right());
116        assert!(r.is_right());
117        assert!(!r.is_left());
118
119        assert_eq!(l.left(), Some(1));
120        assert_eq!(l.right(), None);
121        assert_eq!(r.left(), None);
122        assert_eq!(r.right(), Some("x"));
123    }
124
125    #[test]
126    fn map_left_and_map_right() {
127        let l: Either<i32, &str> = Either::Left(10);
128        let r: Either<i32, &str> = Either::Right("y");
129
130        assert_eq!(l.map_left(|v| v * 2), Either::Left(20));
131        assert_eq!(l.map_right(str::len), Either::Left(10));
132        assert_eq!(r.map_left(|v| v * 2), Either::Right("y"));
133        assert_eq!(r.map_right(str::len), Either::Right(1));
134    }
135
136    #[test]
137    fn as_ref_and_derives() {
138        let l: Either<i32, &str> = Either::Left(5);
139        let copied = l; // Copy
140        assert_eq!(l, copied); // PartialEq
141        assert_eq!(l.as_ref(), Either::Left(&5));
142        let cloned = l.clone(); // Clone (explicit for coverage)
143        assert_eq!(format!("{cloned:?}"), "Left(5)"); // Debug
144    }
145}