Skip to main content

amalgam/
maybe.rs

1//! The [`MaybeValue`] type: "a value, or explicitly no value".
2
3/// A value that may or may not be present.
4///
5/// This is the Rust counterpart of FusionCache's `MaybeValue<T>`. It is a thin,
6/// purpose-named wrapper around [`Option`] so the public API reads the same as
7/// FusionCache (`has_value`, `value_or_default`, `from_value`, …) while still
8/// interoperating freely with idiomatic `Option` via [`From`].
9///
10/// It appears in two places:
11/// * the return type of [`Cache::try_get`](crate::Cache::try_get) — a miss is
12///   [`MaybeValue::none`], distinct from "present but `None`/null";
13/// * the `fail_safe_default` argument of
14///   [`Cache::get_or_set`](crate::Cache::get_or_set) — a last-resort value used
15///   only when the factory fails and no stale entry exists.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct MaybeValue<V>(Option<V>);
18
19impl<V> MaybeValue<V> {
20    /// A present value.
21    #[must_use]
22    pub fn from_value(value: V) -> Self {
23        Self(Some(value))
24    }
25
26    /// The absence of a value.
27    #[must_use]
28    pub fn none() -> Self {
29        Self(None)
30    }
31
32    /// `true` if a value is present.
33    #[must_use]
34    pub fn has_value(&self) -> bool {
35        self.0.is_some()
36    }
37
38    /// Borrows the contained value, if any.
39    #[must_use]
40    pub fn value(&self) -> Option<&V> {
41        self.0.as_ref()
42    }
43
44    /// Consumes the wrapper, returning the contained value, if any.
45    #[must_use]
46    pub fn into_value(self) -> Option<V> {
47        self.0
48    }
49
50    /// Returns the contained value or `default` when absent.
51    #[must_use]
52    pub fn value_or(self, default: V) -> V {
53        self.0.unwrap_or(default)
54    }
55
56    /// Maps the contained value, preserving absence.
57    #[must_use]
58    pub fn map<U, F: FnOnce(V) -> U>(self, f: F) -> MaybeValue<U> {
59        MaybeValue(self.0.map(f))
60    }
61}
62
63impl<V: Clone> MaybeValue<V> {
64    /// Returns a clone of the contained value or `default` when absent.
65    #[must_use]
66    pub fn value_or_clone(&self, default: V) -> V {
67        self.0.clone().unwrap_or(default)
68    }
69}
70
71impl<V: Default> MaybeValue<V> {
72    /// Returns the contained value or `V::default()` when absent.
73    #[must_use]
74    pub fn value_or_default(self) -> V {
75        self.0.unwrap_or_default()
76    }
77}
78
79impl<V> Default for MaybeValue<V> {
80    /// The default is [`MaybeValue::none`], matching `default(MaybeValue<T>)`.
81    fn default() -> Self {
82        Self(None)
83    }
84}
85
86impl<V> From<V> for MaybeValue<V> {
87    fn from(value: V) -> Self {
88        Self::from_value(value)
89    }
90}
91
92impl<V> From<Option<V>> for MaybeValue<V> {
93    fn from(option: Option<V>) -> Self {
94        Self(option)
95    }
96}
97
98impl<V> From<MaybeValue<V>> for Option<V> {
99    fn from(maybe: MaybeValue<V>) -> Self {
100        maybe.0
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn none_has_no_value() {
110        let m: MaybeValue<i32> = MaybeValue::none();
111        assert!(!m.has_value());
112        assert_eq!(m.value(), None);
113        assert_eq!(MaybeValue::<i32>::default(), MaybeValue::none());
114    }
115
116    #[test]
117    fn from_value_round_trips() {
118        let m = MaybeValue::from_value(42);
119        assert!(m.has_value());
120        assert_eq!(m.value(), Some(&42));
121        assert_eq!(Option::from(m), Some(42));
122    }
123
124    #[test]
125    fn value_or_default_falls_back() {
126        assert_eq!(MaybeValue::<i32>::none().value_or_default(), 0);
127        assert_eq!(MaybeValue::from_value(7).value_or_default(), 7);
128    }
129}