sans, composable coroutine-based programming
sans is a coroutine combinators library written in Rust. Its goal is to provide tools to build composable, resumable computations without compromising on speed, safety, or ergonomics. To that end, it uses Rust's strong typing and ownership system to produce correct, efficient programs, and provides functions, traits and combinators to abstract the error-prone plumbing of stateful computation.
sans will help you build pipelines that yield, resume, and compose beautifully
- Example
- Documentation
- Why use sans?
- Coroutine Combinators
- Technical Features
- Rust Version Requirements (MSRV)
- Installation
- Module Organization
Example
Interactive calculator that maintains state across inputs:
use *;
// Build a stateful calculator that accumulates results
let mut total = 0_i64;
let calculator = init_repeat
.map_input
.map_yield;
// Execute the pipeline
let = calculator.init.unwrap_yielded;
println!; // "total=0"
println!; // "total=5"
println!; // "total=2"
println!; // "total=12"
Chained pipeline with transformation:
use *;
let pipeline = init_once
.map_yield
.chain
.map_return;
let result = handle;
println!; // "Result: 45"
Documentation
- API Documentation
- Examples directory (coming soon)
Why use sans?
If you want to write:
Interactive Protocols
sans was designed for building interactive protocols where computation happens in stages, each stage can yield intermediate results, and the next step depends on external input. Compared to handwritten state machines, sans pipelines are:
- Composable and reusable
- Type-safe with compile-time guarantees
- Free from manual state management bugs
- Easy to test in isolation
Use cases:
- Network protocols with request-response cycles
- REPLs and interactive command processors
- Multi-step wizards and forms
- Game state machines
Streaming Pipelines
sans excels at processing data in stages where each stage can transform, filter, or accumulate results. The type system ensures stages compose correctly, and the coroutine model makes it easy to handle partial data:
- Transform data through multiple processing stages
- Maintain state across stream elements
- Handle backpressure and control flow
- Compose transformations declaratively
Use cases:
- Data processing pipelines
- Stream transformations
- Event sourcing systems
- ETL processes
State Machines
Building explicit state machines with sans is natural and type-safe. Each state is a coroutine that can transition to the next state or complete:
- Explicit state transitions
- Type-safe state representation
- Easy to visualize and debug
- Composable substates
Use cases:
- Protocol implementations
- Workflow engines
- Game AI
- UI navigation flows
Incremental Computation
sans makes it easy to build computations that can be paused, resumed, and composed with other computations:
- Pause computation and resume later
- Compose sub-computations
- Cancel or short-circuit pipelines
- Handle errors at any stage
Use cases:
- Async workflows
- Background jobs that can be paused
- Cooperative multitasking
- Cancellable operations
Coroutine Combinators
Coroutine combinators are an approach to stateful computation that uses small, composable functions instead of complex state machines. Instead of writing a monolithic state machine with explicit state tracking, you compose very small functions with specific purposes like "multiply by 2", or "add 1 and complete", and assemble them into meaningful pipelines.
The resulting code is:
- Small and focused
- Easy to understand
- Close to the specification you'd write naturally
- Highly composable and reusable
This has several advantages:
- Easy to write: Each stage is simple and focused on one task
- Easy to test: Test stages in isolation with unit tests
- Easy to reuse: Stages can be used in multiple pipelines
- Type-safe: The compiler ensures stages compose correctly
- Composable: Build complex behavior from simple pieces
Technical Features
sans provides:
- Type-safe composition: Strong typing ensures stages compose correctly
- Zero-copy: Coroutines don't copy data unnecessarily
- Flexible execution: Both synchronous and asynchronous execution
- Concurrent execution: Run multiple coroutines concurrently with
join - Memory efficient: Stages are dropped after completion to free resources
- Transformations: Map inputs, outputs, and return values
- Method chaining: Fluent API for building pipelines
- No unsafe code: The entire library is
#![forbid(unsafe_code)] - Well tested: Comprehensive test suite with doctests
Rust Version Requirements (MSRV)
sans requires Rustc version 1.85 or greater.
Installation
sans is available on crates.io and can be included in your Cargo enabled project like this:
[]
= "0.1.0-alpha.2"
Then in your code:
use *;
Module Organization
sans is organized by capability - what you want to accomplish:
| Module | Purpose | Key Functions |
|---|---|---|
sans::build |
Creating coroutine stages | once, repeat, from_fn, init_once, init_repeat |
sans::compose |
Combining coroutines | chain, map_input, map_yield, map_return |
sans::concurrent |
Concurrent execution | poll, join, join_vec |
sans::sequential |
Sequential execution | many |
sans::run |
Executing pipelines | handle, handle_async |
sans::prelude |
Common imports | All frequently used items |
Quick Examples
use *;
// Build: Create stages
let stage = once;
let stage = repeat;
let stage = init_once;
// Compose: Chain and transform
let pipeline = stage1.chain;
let transformed = stage.map_yield;
// Run: Execute to completion
let result = handle;
// Concurrent: Run multiple stages
use *;
let mut joined = join;
Core Types
Sans<I, O> - A coroutine that:
- Takes input of type
I - Yields output of type
O - Eventually completes with a
Returnvalue
InitSans<I, O> - A coroutine that:
- Provides an initial output before processing input
- Then becomes a
Sans<I, O>coroutine
Step<Y, D> - The result of each step:
Yielded(Y)- Continue with intermediate valueComplete(D)- Finished with final value
Design Decisions
sans is built with a clear set of principles:
Minimal Dependencies
The library maintains as few dependencies as possible. Currently, sans only depends on either for the Either<L, R> type used in generic trait implementations. This keeps the dependency tree small, improves compilation times, and reduces supply chain risk.
No Unsafe Code
sans is #![forbid(unsafe_code)] - the entire library leverages Rust's type system and ownership model to provide safe abstractions. This ensures memory safety and prevents undefined behavior without sacrificing performance.
Coroutine Compatibility
The library's design stays similar to Rust's nightly coroutine syntax and semantics. This intentional alignment means that when coroutines stabilize, sans can potentially interoperate with native coroutine syntax, providing a migration path and compatibility layer.
Stable Rust Only
sans compiles on stable Rust (MSRV 1.65+) without requiring any nightly features. This ensures the library can be used in production environments and maintains compatibility with stable toolchains.
Zero-Cost Abstractions
Coroutines are designed to be as zero-cost as possible:
- No allocations in core combinators (
once,repeat,chain,map_*) - Stack-based state machines
- Inlining and optimization friendly
- States are dropped immediately after completion to free resources
The only allocations occur in dynamic-size operations (join_vec, init_join_vec) which require Vec for runtime-determined numbers of coroutines. Fixed-size operations use stack-allocated arrays.
Inspiration
sans draws inspiration from several excellent projects in the Rust ecosystem:
-
nom - The parser combinator library that pioneered composable, type-safe parsing in Rust. sans applies similar principles to stateful computation and coroutine pipelines.
-
genawaiter - Generator implementation that explores yielding and resumption patterns in Rust.
-
corosensei - Stackful coroutine library demonstrating low-level coroutine control in Rust.
-
propane - Generator and coroutine exploration focusing on ergonomic async patterns.
While these libraries focus on different aspects (parsing, stackful coroutines, async generators), sans synthesizes ideas from all of them to provide a coroutine combinator library focused on composability, type safety, and explicit control flow.
Contributing
- TBD
License
See the LICENSE file in the repository for licensing information.