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