Expand description
§how-far

Guide and interactive examples · API reference
A small no_std + alloc protocol for cooperative cancellation and approximate
progress. Libraries accept &dyn Pulse and return their own ordinary Result.
No tracker, clock, executor, serialization, or diagnostic collector is required.
Rust 1.88, like the other how-far crates; pointer atomics and an allocator are
required.
[dependencies]
how-far = "0.1.0"§One operation
Accept a pulse, check before starting, and report work after doing it:
use how_far::{prelude::*, StopReason};
fn checksum(bytes: &[u8], pulse: &dyn Pulse) -> Result<u64, StopReason> {
pulse.check()?;
let mut sum = 0;
for &byte in bytes {
sum += u64::from(byte);
pulse.step(1)?;
}
Ok(sum)
}
assert_eq!(checksum(b"hello", &how_far::NoPulse), Ok(532));Use the same function with &how_far::NoPulse, a tracker from how-far-along,
or &StopOnly::borrowed(&token) for cancellation alone. The library never
finishes the borrowed input pulse: its caller owns that result handoff.
See the application quickstart.
§Several operations
Define the stages once, bind their reservations to variables, and pass each active handle to its operation. The operations can take different inputs and return different types:
use how_far::{prelude::*, PhaseSpec, Stages, StopReason, Total};
fn prepare(bytes: &mut [u8], pulse: &dyn Pulse) -> Result<u64, StopReason> {
let (mut stages, [invert, checksum]) = Stages::define(pulse, [
PhaseSpec::new("invert", 1, Total::Exact(bytes.len() as u64)),
PhaseSpec::new("checksum", 1, Total::Exact(bytes.len() as u64)),
]);
let invert = stages.begin(invert);
invert_bytes(bytes, &invert).finish_phase(invert)?;
let checksum = stages.begin(checksum);
let sum = sum_bytes(bytes, &checksum).finish_phase(checksum)?;
stages.complete(Ok(sum))
}
fn invert_bytes(bytes: &mut [u8], pulse: &dyn Pulse) -> Result<(), StopReason> {
pulse.check()?;
for byte in bytes {
*byte = !*byte;
pulse.step(1)?;
}
Ok(())
}
fn sum_bytes(bytes: &[u8], pulse: &dyn Pulse) -> Result<u64, StopReason> {
pulse.check()?;
let mut sum = 0;
for &byte in bytes {
sum += u64::from(byte);
pulse.step(1)?;
}
Ok(sum)
}
assert_eq!(prepare(&mut [0, 255], &how_far::NoPulse), Ok(255));Stages::define returns the plan and an array of StageKey reservations.
Bind them to variables; stages.begin(key) gives the next active handle.
Pass it to an ordinary function and hand the result back with
.finish_phase(stage)?. Keys are specific to one plan and checked in order;
a mismatch records StageMismatch and runs untracked without consuming the
next declaration. Labels are only for display. A failed or cancelled handoff
makes remaining stages NotRun, even when ? returns immediately. Call
stages.complete(Ok(value)) on success to mark unused optional stages Skipped.
Within an active stage, stage.fork([specs...]) starts independent branch
handles. Each takes its own data and uses the same result handoff. Schedule
and join them with your threading or async library, then hand the combined
result to the enclosing stage. Branch failure does not stop siblings by itself.
For runtime-sized repeated work, Stages::new plus begin_next() uses the
same handles without named reservations. See the
fork–join example.
Complete::complete(result) and ResultExt::finish_phase(owner) are explicit
result handoffs, returning the original value/error unchanged. Untouched phases
become Skipped on success or NotRun on failure. Drop without a result records
Abandoned. An unfinished active stage also marks the remaining sequence
NotRun. Put work that can fail inside the operation whose result is handed
over: a bare ? before that handoff cannot classify its error for the tracker.
A borrowed pulse belongs to its caller: complete your child owners and plan,
never the borrowed input.
§Adapting existing code
Stages::run and Complete::complete_with remain optional closure conveniences.
The latter hands over a multi-step body’s result, including early ? returns.
For a custom error, implement From<StopReason> for ? and enough::AsStopReason
(re-exported here) for cancellation classification; the latter only inspects
the error. Forgetting it is a compile error that names the fix. Bare
StopReason works directly. For a foreign error type you cannot implement
AsStopReason for, pass a classifier to run_classified, complete_classified or
complete_with_classified.
check invokes cancellation/checkpoint policy; advance only counts; step
counts then checks. One Paced per worker batches reports. Its finish() flushes
and checks the final partial batch. Its Drop only flushes; it cannot return a
cancellation error. Counters do not synchronize application data.
Phases runs independently selected phases, allowing a library to catch a failed
attempt and run a fallback. These plan helpers introduce no planning error into the
work’s result. Plans rejected by an observer retain cancellation/checkpoint
behavior through untracked children. record_issue lets an optional diagnostic
wrapper retain evidence of rejected observations.
share() preserves the complete Pulse interface for owned tasks but grants no
completion rights. Join workers before completing their original owner.
Unsupported sharing is an explicit NotShareable error, never a silent loss of
cancellation. Pulse::split, start, set_total,
and Child::finish are the explicitly fallible administration layer.
TryStages and its RunError wrapper provide an explicitly fallible runner.
All core APIs are available without Cargo features.
WithStop preserves an additional stop policy through children and owned views.
WithStop::borrowed(pulse, stop)
combines a caller’s pulse with a local &dyn Stop; replacing_borrowed uses only
the new policy (bypassing the inner check and its callbacks). Borrowed policies
work with scoped workers; owned new/replacing policies can use share(). Tracking and lazy callbacks live in
how-far-along; profiling and diagnostics live in how-far-really.
Runnable, cross-crate usage and intentional mistakes are in the example crates.
See the tested integration guide for dependency setup, result
handoffs, callbacks, diagnostics, and caller-side display smoothing. Run it
with just check-integration from the workspace root.
Modules§
- prelude
- The traits whose methods library code calls:
use how_far::prelude::*;.
Structs§
- Child
- A planned child phase, owned by the code that called
Pulse::split. - Inert
- The type of
NoPulse. It has exactly one value, that static: nothing outside this crate can build, copy or move one. - NoReport
- Discards every report. It is zero-sized and compiles away in generic code.
- Paced
- Counts completed work locally and reaches its pulse once every
everyunits: it reports the units counted so far, then checks for cancellation. - Phase
Spec - One child in a split: a name, a weight relative to its siblings, a total, and optionally a unit name and an execution model.
- Phases
- A fixed set of owned phases, selected by index rather than automatic sequence.
A failed attempt leaves other phases available. The library decides whether
to recover, retry, or propagate its error. Hand the final result to
completeto resolve untouched phases; Drop without that handoff records abandonment. - Progress
With Stop - A stop policy and a progress sink used as one value.
- Shared
Pulse - An owned, cloneable pulse that retains planning and cancellation capabilities. The original phase owner alone may finish it. Requires pointer atomics.
- Stage
- One active sequential stage, borrowing its plan until result handoff.
- Stage
Key - A reservation identifying one stage in one
Stages::defineplan. - Stages
- Sequential work returning the library’s ordinary
Result<T, E>. - Stop
Only - Cancellation without progress: a pulse made from any
Stop. - TryStages
- Run a sequential plan one stage at a time.
- Unstoppable
- A
Stopimplementation that never stops (no cooperative cancellation). - With
Stop - Adds or replaces a pulse’s checkpoint policy, including through descendants.
Enums§
- Execution
- How a phase’s work is scheduled. Declaring it schedules nothing.
- Outcome
- How a phase ended. Reaching a count total is not an outcome.
- Plan
Error - A phase plan that cannot be applied, or a lifecycle step out of order.
- RunError
- An operation error, or an error applying the progress plan.
- Stop
Reason - Why an operation was stopped.
- Total
- The denominator for completed work in one phase.
Statics§
- NoPulse
- Never stops and discards reports, including in every nested phase.
Traits§
- AsStop
Reason - The
StopReasonan error represents, if it represents one. - Child
Pulse - What a pulse’s children add: publishing a terminal outcome.
- Complete
- An owner that can record a result without changing the operation’s result. Reporting problems are diagnostic evidence, never replacement work errors.
- Progress
Ext - Checkpoints for anything that both checks cancellation and counts work,
including
&dyn Pulse,Child, andProgressWithStop. - Pulse
- Cancellation, completed work, and nested phase planning, in one value.
- Report
- A sink for completed units of work.
- Result
Ext - The explicit boundary where a normal Rust result completes its phase owner.
- Stop
- Cooperative cancellation check.