Cancellation Is a Cooperative Problem Not a Unilateral One
You cannot safely cancel an operation that does not cooperate with its own cancellation. This is the central truth that every language's cancellation mechanism must confront, and the design choices they make reveal deep differences in philosophy about control flow, resource ownership, and safety.
The spectrum runs from fully cooperative to almost-automatic. C# (.NET) pioneered CancellationToken an explicit, cooperative pattern where the caller passes a token and the callee periodically checks it. Clean and composable, but the callee can ignore it entirely. Python's asyncio follows the same cooperative model with less structure. Go embeds cancellation into its context package every goroutine receives a context, and cancellation propagates through the call tree. It is cooperative but culturally enforced: idiomatic Go checks ctx.Done() everywhere.
Rust takes a radically different approach by making cancellation a consequence of ownership. When a future is dropped, it is cancelled and Rust's type system guarantees cleanup through Drop. This is the closest any language gets to safe automatic cancellation, but it introduces its own subtlety: if a future is dropped mid-await, any partially completed work must be safe to abandon. Zig takes the opposite extreme: cancellation is fully manual and explicit, reflecting its philosophy that nothing should happen implicitly.
JavaScript's AbortController mirrors C#'s CancellationToken for the browser and Node.js worlds. C++ added std::stop_token in C++20 cooperative cancellation bolted onto a language that historically had none, reflecting the difficulty of retrofitting safety.
The deeper pattern across all these approaches: structured concurrency makes cancellation safer by tying task lifetimes to scopes. When a parent scope ends, all child tasks are cancelled and awaited. This prevents fire-and-forget goroutines or detached futures from leaking resources. Languages that enforce structured concurrency (Rust's futures, Go's errgroup patterns, Python's TaskGroup) handle cancellation most cleanly because the lifetime relationships are explicit.
Takeaway: The hardest part of cancellation is not signaling the request it is ensuring that partially completed work is cleaned up correctly, which is why the best cancellation designs tie task lifetimes to scope boundaries rather than relying on manual checking.
See also: Cognitive Load Is the Real Bottleneck in System Design | Complexity Has Three Sources | Gall's Law Complex Systems Must Evolve From Simple Ones | Pluralistic Ignorance Sustains Norms Nobody Believes