computation_process/
computable.rs

1use crate::{Completable, DynComputable, Incomplete};
2use cancel_this::Cancellable;
3
4/// A generic trait implemented by types that represent a "computation".
5///
6/// To advance the computation, repeatedly call [`Computable::try_compute`] until a value is
7/// returned. Once the value is returned, the computable becomes "exhausted" and will return
8/// [`Incomplete::Exhausted`].
9///
10/// See also [`ComputableResult`] and [`crate::Computation`].
11pub trait Computable<T> {
12    /// Try to advance this computation, returning a value once the computation is done.
13    fn try_compute(&mut self) -> Completable<T>;
14
15    /// Advance this computation until it either completes, is canceled, or becomes exhausted,
16    /// skipping over all suspended states.
17    ///
18    /// This method is identical to repeatedly calling [`Computable::try_compute`] until it
19    /// returns something other than [`Incomplete::Suspended`].
20    ///
21    /// Note that this method can loop forever if the computation never completes and keeps
22    /// returning [`Incomplete::Suspended`].
23    fn compute_completable(&mut self) -> Completable<T> {
24        loop {
25            match self.try_compute() {
26                Ok(value) => return Ok(value),
27                Err(Incomplete::Suspended) => continue,
28                Err(e) => return Err(e),
29            }
30        }
31    }
32
33    /// Advance this computation until completion, skipping over all suspended states.
34    ///
35    /// # Panics
36    ///
37    /// Panics if called on an exhausted computation, i.e., if [`Computable::try_compute`] returns
38    /// [`Incomplete::Exhausted`]. If you want to handle exhaustion gracefully, use
39    /// [`Computable::compute_completable`] instead.
40    fn compute(&mut self) -> Cancellable<T> {
41        match self.compute_completable() {
42            Ok(value) => Ok(value),
43            Err(Incomplete::Suspended) => unreachable!(
44                "`compute_completable` never returns `Incomplete::Suspended` by definition."
45            ),
46            Err(Incomplete::Cancelled(c)) => Err(c),
47            Err(Incomplete::Exhausted) => panic!("Called `compute` on an exhausted `Computable`."),
48        }
49    }
50
51    /// Utility method to convert this [`Computable`] to a dynamic type.
52    fn dyn_computable(self) -> DynComputable<T>
53    where
54        Self: Sized + 'static,
55    {
56        Box::new(self)
57    }
58}
59
60/// A result-like object that stores the result of a [`Computable`] for later use.
61#[derive(Debug, Clone, PartialEq, Eq)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
63pub struct ComputableResult<T, C: Computable<T>> {
64    computable: C,
65    result: Option<T>,
66}
67
68impl<T, C: Computable<T>> From<C> for ComputableResult<T, C> {
69    fn from(value: C) -> Self {
70        ComputableResult {
71            computable: value,
72            result: None,
73        }
74    }
75}
76
77impl<T, C: Computable<T>> ComputableResult<T, C> {
78    /// Create a new [`ComputableResult`] from an instance of [`Computable`].
79    pub fn new(computable: C) -> Self {
80        computable.into()
81    }
82
83    /// Advance the inner [`Computable`] and return its result or return a reference
84    /// to the already computed result.
85    pub fn try_compute(&mut self) -> Completable<&T> {
86        if self.result.is_none() {
87            let result = self.computable.try_compute()?;
88            self.result = Some(result);
89        }
90
91        if let Some(result) = self.result.as_ref() {
92            return Ok(result);
93        }
94
95        unreachable!("Both `result` and `computable` cannot be `None`.")
96    }
97
98    /// A reference to the computed result, assuming it is already available.
99    pub fn result_ref(&self) -> Option<&T> {
100        self.result.as_ref()
101    }
102
103    /// The computed result, assuming it is already available.
104    pub fn result(self) -> Option<T> {
105        self.result
106    }
107
108    /// A reference to the underlying computation, assuming it is still available.
109    pub fn computable_ref(&self) -> &C {
110        &self.computable
111    }
112
113    /// The underlying computation, assuming it is still available.
114    pub fn computable(self) -> C {
115        self.computable
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use crate::{ComputableIdentity, Incomplete};
123
124    #[test]
125    fn test_computable_result_from() {
126        let identity: ComputableIdentity<i32> = 42.into();
127        let result: ComputableResult<i32, ComputableIdentity<i32>> = identity.into();
128        assert!(result.result_ref().is_none());
129    }
130
131    #[test]
132    fn test_computable_result_new() {
133        let identity: ComputableIdentity<i32> = 100.into();
134        let mut result = ComputableResult::new(identity);
135        assert!(result.result_ref().is_none());
136
137        let computed = result.try_compute().unwrap();
138        assert_eq!(*computed, 100);
139        assert_eq!(result.result_ref(), Some(&100));
140    }
141
142    #[test]
143    fn test_computable_result_try_compute_multiple_times() {
144        let identity: ComputableIdentity<String> = "test".to_string().into();
145        let mut result = ComputableResult::new(identity);
146
147        let first = result.try_compute().unwrap();
148        assert_eq!(*first, "test");
149        let first_ptr = first as *const String;
150
151        // The second call should return the same reference
152        let second = result.try_compute().unwrap();
153        assert_eq!(*second, "test");
154        let second_ptr = second as *const String;
155        assert_eq!(first_ptr, second_ptr);
156    }
157
158    #[test]
159    fn test_computable_result_result() {
160        let identity: ComputableIdentity<i32> = 42.into();
161        let mut result = ComputableResult::new(identity);
162        let _ = result.try_compute().unwrap();
163
164        let value = result.result();
165        assert_eq!(value, Some(42));
166    }
167
168    #[test]
169    fn test_computable_result_result_none() {
170        let identity: ComputableIdentity<i32> = 42.into();
171        let result = ComputableResult::new(identity);
172        let value = result.result();
173        assert_eq!(value, None);
174    }
175
176    #[test]
177    fn test_computable_result_computable_ref() {
178        let identity: ComputableIdentity<i32> = 42.into();
179        let result = ComputableResult::new(identity);
180        let _computable_ref = result.computable_ref();
181    }
182
183    #[test]
184    fn test_computable_result_computable() {
185        let identity: ComputableIdentity<i32> = 42.into();
186        let result = ComputableResult::new(identity);
187        let mut computable = result.computable();
188        let value = computable.try_compute().unwrap();
189        assert_eq!(value, 42);
190    }
191
192    #[test]
193    fn test_dyn_computable() {
194        let identity: ComputableIdentity<i32> = 42.into();
195        let mut dyn_computable = identity.dyn_computable();
196        let result = dyn_computable.try_compute().unwrap();
197        assert_eq!(result, 42);
198    }
199
200    #[test]
201    fn test_compute_method() {
202        let mut identity: ComputableIdentity<i32> = 42.into();
203        let result = identity.compute().unwrap();
204        assert_eq!(result, 42);
205    }
206
207    // Test with a computable that suspends
208    struct SuspendingComputable {
209        count: u32,
210        target: u32,
211    }
212
213    impl Computable<u32> for SuspendingComputable {
214        fn try_compute(&mut self) -> Completable<u32> {
215            self.count += 1;
216            if self.count < self.target {
217                Err(Incomplete::Suspended)
218            } else {
219                Ok(self.count)
220            }
221        }
222    }
223
224    #[test]
225    fn test_compute_with_suspensions() {
226        let mut computable = SuspendingComputable {
227            count: 0,
228            target: 3,
229        };
230        let result = computable.compute().unwrap();
231        assert_eq!(result, 3);
232    }
233
234    #[test]
235    fn test_try_compute_with_suspensions() {
236        let mut computable = SuspendingComputable {
237            count: 0,
238            target: 3,
239        };
240
241        // The first call should suspend
242        assert_eq!(computable.try_compute(), Err(Incomplete::Suspended));
243        // The second call should suspend
244        assert_eq!(computable.try_compute(), Err(Incomplete::Suspended));
245        // The third call should complete
246        assert_eq!(computable.try_compute(), Ok(3));
247    }
248}