Stillwater
"Still waters run pure"
A Rust library for pragmatic effect composition and validation, emphasizing the pure core, imperative shell pattern.
Philosophy
Stillwater embodies a simple idea:
- Still = Pure functions (unchanging, referentially transparent)
- Water = Effects (flowing, performing I/O)
Keep your business logic pure and calm like still water. Let effects flow at the boundaries.
What Problems Does It Solve?
1. "I want ALL validation errors, not just the first one"
use Validation;
// Standard Result: stops at first error ❌
let email = validate_email?; // Stops here
let age = validate_age?; // Never reached if email fails
// Stillwater: accumulates all errors ✓
let user = all?;
// Returns: Err(vec![EmailError, AgeError, NameError])
2. "How do I test code with database calls?"
use Effect;
// Pure business logic (no DB, easy to test)
// Effects at boundaries (mockable)
// Test with mock environment
3. "My errors lose context as they bubble up"
use Effect;
fetch_user
.context
.and_then
.context
.run?;
// Error output:
// Error: UserNotFound(12345)
// -> Loading user profile
// -> Processing user data
Core Features
Validation<T, E>- Accumulate all errors instead of short-circuitingEffect<T, E, Env>- Separate pure logic from I/O effectsSemigrouptrait - Associative combination of valuesMonoidtrait - Identity elements for powerful composition patterns- Context chaining - Never lose error context
- Zero-cost abstractions - Compiles to same code as hand-written
- Works with
?operator - Integrates with Rust idioms - No heavy macros - Clear types, obvious behavior
Quick Start
use ;
// 1. Validation with error accumulation
// 2. Effect composition
// 3. Run at application boundary
let env = AppEnv ;
let result = create_user.run?;
Why Stillwater?
Compared to existing solutions:
vs. frunk:
- ✓ Focused on practical use cases, not type-level programming
- ✓ Better documentation and examples
- ✓ Effect composition, not just validation
vs. monadic:
- ✓ No awkward macro syntax (
rdrdo! { ... }) - ✓ Zero-cost (no boxing by default)
- ✓ Idiomatic Rust, not Haskell port
vs. hand-rolling:
- ✓ Validation accumulation built-in
- ✓ Error context handling
- ✓ Testability patterns established
- ✓ Composable, reusable
What makes it "Rust-first":
- ❌ No attempt at full monad abstraction (impossible without HKTs)
- ✓ Works with
?operator viaTrytrait - ✓ Zero-cost via generics and monomorphization
- ✓ Integrates with async/await
- ✓ Borrows checker friendly
- ✓ Clear error messages
Installation
Add to your Cargo.toml:
[]
= "0.2"
# Optional: async support
= { = "0.2", = ["async"] }
Examples
Run any example with cargo run --example <name>:
| Example | Demonstrates |
|---|---|
| form_validation | Validation error accumulation |
| user_registration | Effect composition and I/O separation |
| error_context | Error trails for debugging |
| data_pipeline | Real-world ETL pipeline |
| testing_patterns | Testing pure vs effectful code |
| validation | Validation type and error accumulation patterns |
| effects | Effect type and composition patterns |
| io_patterns | IO module helpers for reading/writing |
| pipeline | Data transformation pipelines |
| monoid | Monoid and Semigroup traits for composition |
See examples/ directory for full code.
Production Readiness
Status: 0.2 - Production Ready for Early Adopters
- ✅ 152 unit tests passing (includes property-based tests)
- ✅ 68 documentation tests passing
- ✅ Zero clippy warnings
- ✅ Comprehensive examples
- ✅ Full async support
- ✅ CI/CD pipeline with security audits
This library is stable and ready for use. The 0.x version indicates the API may evolve based on community feedback.
Documentation
- 📚 User Guide - Comprehensive tutorials
- 📖 API Docs - Full API reference
- 🤔 FAQ - Common questions
- 🏛️ Design - Architecture and decisions
- 💭 Philosophy - Core principles
- 🎯 Patterns - Common patterns and recipes
- 🔄 Comparison - vs other libraries
Migrating from Result
Already using Result everywhere? No problem! Stillwater integrates seamlessly:
// Your existing code works as-is
// Upgrade to accumulation when you need it
// Convert back to Result when needed
let result: = validation.into_result;
Start small, adopt progressively. Use Validation only where you need error accumulation.
Contributing
Contributions welcome! This is a young library with room to grow:
- 🐛 Bug reports and feature requests via issues
- 📖 Documentation improvements
- 🧪 More examples and use cases
- 💡 API feedback and design discussions
Before submitting PRs, please open an issue to discuss the change.
License
MIT © Glen Baker iepathos@gmail.com
"Like a still pond with water flowing through it, stillwater keeps your pure business logic calm and testable while effects flow at the boundaries."