Skip to main content

how_far/
completion.rs

1//! Transfer the final work result without converting or consuming its error.
2use crate::{AsStopReason, Outcome};
3
4/// An owner that can record a result without changing the operation's result.
5/// Reporting problems are diagnostic evidence, never replacement work errors.
6pub trait Complete: Sized {
7    /// Record an explicitly chosen outcome, resolving unfinished observations.
8    /// Join workers before calling this. Started but uncompleted children are
9    /// abandoned; untouched children are skipped on success, not run on error.
10    fn complete_as(self, outcome: Outcome);
11
12    /// Classify the error with [`AsStopReason`], record completion, and return the
13    /// original result.
14    ///
15    /// Forgetting `AsStopReason` for a library's error is reported as that trait
16    /// being unimplemented, not as a type mismatch elsewhere:
17    ///
18    /// ```compile_fail,E0277
19    /// use how_far::{prelude::*, PhaseSpec, Stages, StopReason, Total};
20    ///
21    /// enum Error {
22    ///     Stopped(StopReason),
23    /// }
24    /// impl From<StopReason> for Error {
25    ///     fn from(reason: StopReason) -> Self {
26    ///         Self::Stopped(reason)
27    ///     }
28    /// }
29    /// fn encode(pulse: &dyn Pulse) -> Result<(), Error> {
30    ///     Stages::new(pulse, &[PhaseSpec::new("work", 1, Total::Unknown)])
31    ///         .complete_with(|stages| stages.run(|stage| Ok(stage.check()?)))
32    /// }
33    /// ```
34    fn complete<T, E>(self, result: Result<T, E>) -> Result<T, E>
35    where
36        E: AsStopReason,
37    {
38        self.complete_classified(result, |error| error.as_stop_reason().is_some())
39    }
40
41    /// Complete with an explicit classifier, for a foreign error type that
42    /// Rust's orphan rules keep from implementing [`AsStopReason`].
43    fn complete_classified<T, E>(
44        self,
45        result: Result<T, E>,
46        is_stop: impl FnOnce(&E) -> bool,
47    ) -> Result<T, E> {
48        self.complete_as(Outcome::from_result(&result, is_stop));
49        result
50    }
51
52    /// Run a multi-step `body` with this owner, then complete the owner with
53    /// the body's result and return that result unchanged.
54    ///
55    /// An early `?` inside `body` still reaches the handoff, which a `?` placed
56    /// before a separate `complete` call would bypass. A panic in `body` drops
57    /// the owner, recording abandonment as usual.
58    ///
59    /// ```
60    /// use how_far::{prelude::*, PhaseSpec, Stages, StopReason, Total};
61    ///
62    /// fn convert(pulse: &dyn Pulse) -> Result<u64, StopReason> {
63    ///     Stages::new(pulse, &[
64    ///         PhaseSpec::new("decode", 1, Total::Exact(1)),
65    ///         PhaseSpec::new("sharpen", 1, Total::Exact(1)),
66    ///     ])
67    ///     .complete_with(|stages| {
68    ///         let pixels = stages.run(|stage| stage.step(1).map(|()| 7))?;
69    ///         // An untouched "sharpen" stage is resolved as Skipped.
70    ///         Ok(pixels)
71    ///     })
72    /// }
73    /// assert_eq!(convert(&how_far::NoPulse), Ok(7));
74    /// ```
75    fn complete_with<T, E>(mut self, body: impl FnOnce(&mut Self) -> Result<T, E>) -> Result<T, E>
76    where
77        E: AsStopReason,
78    {
79        let result = body(&mut self);
80        self.complete(result)
81    }
82
83    /// [`complete_with`](Self::complete_with) with an explicit classifier, for
84    /// a foreign error type that cannot implement [`AsStopReason`], such as an error
85    /// wrapped with its source location.
86    fn complete_with_classified<T, E>(
87        mut self,
88        is_stop: impl FnOnce(&E) -> bool,
89        body: impl FnOnce(&mut Self) -> Result<T, E>,
90    ) -> Result<T, E> {
91        let result = body(&mut self);
92        self.complete_classified(result, is_stop)
93    }
94}
95
96/// The explicit boundary where a normal Rust result completes its phase owner.
97pub trait ResultExt<T, E>: Sized {
98    /// Record completion and return this result unchanged. This is not invoked
99    /// by `?`, `map`, or Drop automatically.
100    fn finish_phase(self, owner: impl Complete) -> Result<T, E>
101    where
102        E: AsStopReason;
103}
104impl<T, E> ResultExt<T, E> for Result<T, E> {
105    fn finish_phase(self, owner: impl Complete) -> Result<T, E>
106    where
107        E: AsStopReason,
108    {
109        owner.complete(self)
110    }
111}