enough 0.4.3

Minimal cooperative cancellation trait for long-running operations
Documentation

enough

Minimal cooperative cancellation trait for Rust.

CI Crates.io docs.rs codecov License MSRV

A no_std, zero-dependency trait for cooperative cancellation.

The Trait

pub trait Stop: Send + Sync {
    /// Check if the operation should stop.
    /// Returns Ok(()) to continue, Err(StopReason) to stop.
    fn check(&self) -> Result<(), StopReason>;

    /// Returns true if the operation should stop (provided).
    fn should_stop(&self) -> bool { self.check().is_err() }

    /// Returns true if this stop can ever fire (provided).
    /// Unstoppable returns false. Used by StopToken/BoxedStop to
    /// optimize away no-op stops at construction time.
    fn may_stop(&self) -> bool { true }
}

One required method. Option<T: Stop> implements Stop: None is a no-op, Some delegates — enabling the may_stop() optimization pattern (see below).

Quick Start

Accept impl Stop + 'static in your public API. Use StopToken from almost-enough internally — it handles the Unstoppable optimization automatically and is the fastest option for real stop types:

use enough::Stop;
use almost_enough::StopToken;

pub fn decode(data: &[u8], stop: impl Stop + 'static) -> Result<Vec<u8>, MyError> {
    let stop = StopToken::new(stop); // Unstoppable → None (no alloc). Stopper → same Arc.
    for (i, chunk) in data.chunks(1024).enumerate() {
        if i % 16 == 0 {
            stop.check()?; // Unstoppable: no-op. Stopper: one dispatch.
        }
        // process...
    }
    Ok(vec![])
}

// Callers:
// decode(&data, Unstoppable)?;   // no cancellation — zero cost
// decode(&data, stopper)?;       // with cancellation

StopToken is Clone (Arc increment) for thread fan-out. Stopper/SyncStopper convert to StopToken at zero cost via Into (same Arc, no double-wrapping). Benchmarks show StopToken within 3% of fully-inlined generic for Unstoppable, and 25% faster than generic for Stopper.

Without almost-enough

Use &dyn Stop with may_stop().then_some():

fn inner(data: &[u8], stop: &dyn Stop) -> Result<(), MyError> {
    let stop = stop.may_stop().then_some(stop); // Option<&dyn Stop>
    for (i, chunk) in data.chunks(1024).enumerate() {
        if i % 16 == 0 {
            stop.check()?; // None → Ok(()), Some → one dispatch
        }
    }
    Ok(())
}

Embedded / no_std

Use impl Stop (without 'static) to accept borrowed types like StopRef<'a>:

fn process(data: &[u8], stop: impl Stop) -> Result<(), StopReason> {
    for (i, byte) in data.iter().enumerate() {
        if i % 64 == 0 { stop.check()?; }
    }
    Ok(())
}

Crate Structure

Crate Purpose
enough Core trait: Stop, StopReason, Unstoppable
almost-enough All implementations: Stopper, StopToken, StopSource, timeouts, combinators
enough-ffi C FFI for cross-language use
enough-tokio Bridge to tokio's CancellationToken

Features

  • None (default) - no_std core: Stop trait, StopReason, Unstoppable
  • alloc - Adds Box<T> and Arc<T> blanket impls for Stop
  • std - Implies alloc (kept for downstream compatibility)

License

MIT OR Apache-2.0