stillwater 1.0.0

Pragmatic effect composition and validation for Rust - pure core, imperative shell
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
# Stillwater

[![Crates.io](https://img.shields.io/crates/v/stillwater)](https://crates.io/crates/stillwater)
[![Downloads](https://img.shields.io/crates/d/stillwater)](https://crates.io/crates/stillwater)
[![CI](https://github.com/iepathos/stillwater/actions/workflows/ci.yml/badge.svg)](https://github.com/iepathos/stillwater/actions/workflows/ci.yml)
[![License](https://img.shields.io/badge/license-MIT)](LICENSE)

A Rust library for pragmatic effect composition and validation, emphasizing the **pure core, imperative shell** pattern.

## Philosophy

**Stillwater** embodies a simple idea:
- Pure functions (unchanging, referentially transparent)
- 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"

```rust
use stillwater::Validation;

// Standard Result: stops at first error
let email = validate_email(input)?;  // Stops here
let age = validate_age(input)?;      // Never reached if email fails

// Stillwater: accumulates all errors
let user = Validation::all((
    validate_email(input),
    validate_age(input),
    validate_name(input),
))?;
// Returns: Err(vec![EmailError, AgeError, NameError])
```

### 2. "How do I validate that all items have the same type before combining?"

```rust
use stillwater::validation::homogeneous::validate_homogeneous;
use std::mem::discriminant;

#[derive(Clone, Debug, PartialEq)]
enum Aggregate {
    Sum(f64),     // Can combine Sum + Sum
    Count(usize), // Can combine Count + Count
    // But Sum + Count is a type error!
}

// Without validation: runtime panic
let mixed = vec![Aggregate::Count(5), Aggregate::Sum(10.0)];
// items.into_iter().reduce(|a, b| a.combine(b))  // PANIC!

// With validation: type-safe error accumulation
let result = validate_homogeneous(
    mixed,
    |a| discriminant(a),
    |idx, _, _| format!("Type mismatch at index {}", idx),
);

match result {
    Validation::Success(items) => {
        // Safe to combine - all same type!
        let total = items.into_iter().reduce(|a, b| a.combine(b));
    }
    Validation::Failure(errors) => {
        // All mismatches reported: ["Type mismatch at index 1"]
    }
}
```

### 3. "How do I test code with database calls?"

```rust
use stillwater::prelude::*;

// Pure business logic (no DB, easy to test)
fn calculate_discount(customer: &Customer, total: Money) -> Money {
    match customer.tier {
        Tier::Gold => total * 0.15,
        _ => total * 0.05,
    }
}

// Effects at boundaries (mockable) - zero-cost by default
fn process_order(id: OrderId) -> impl Effect<Output = Invoice, Error = AppError, Env = AppEnv> {
    from_fn(move |env: &AppEnv| env.db.fetch_order(id))  // I/O
        .and_then(|order| {
            let total = calculate_total(&order);  // Pure!
            from_fn(move |env: &AppEnv| env.db.fetch_customer(order.customer_id))
                .map(move |customer| (order, customer, total))
        })
        .map(|(order, customer, total)| {
            let discount = calculate_discount(&customer, total);  // Pure!
            create_invoice(order.id, total - discount)            // Pure!
        })
        .and_then(|invoice| from_fn(move |env: &AppEnv| env.db.save(invoice))) // I/O
}

// Test with mock environment
#[tokio::test]
async fn test_with_mock_db() {
    let env = MockEnv::new();
    let result = process_order(id).run(&env).await?;
    assert_eq!(result.total, expected);
}
```

### 4. "I need to fetch multiple independent resources"

```rust
use stillwater::prelude::*;

// Combine independent effects - neither depends on the other
fn load_user_profile(id: UserId) -> impl Effect<Output = UserProfile, Error = AppError, Env = AppEnv> {
    fetch_user(id)
        .zip(fetch_settings(id))
        .zip(fetch_preferences(id))
        .map(|((user, settings), prefs)| UserProfile { user, settings, prefs })
}

// Or use zip3 for cleaner flat tuples
fn load_user_profile_v2(id: UserId) -> impl Effect<Output = UserProfile, Error = AppError, Env = AppEnv> {
    zip3(
        fetch_user(id),
        fetch_settings(id),
        fetch_preferences(id),
    )
    .map(|(user, settings, prefs)| UserProfile { user, settings, prefs })
}

// Combine results with a function directly using zip_with
let effect = fetch_price(item_id)
    .zip_with(fetch_quantity(item_id), |price, qty| price * qty);
```

### 5. "My errors lose context as they bubble up"

```rust
use stillwater::prelude::*;

fetch_user(id)
    .context("Loading user profile")
    .and_then(|user| process_data(user))
    .context("Processing user data")
    .run(&env).await?;

// Error output:
// Error: UserNotFound(12345)
//   -> Loading user profile
//   -> Processing user data
```

### 6. "I need clean dependency injection without passing parameters everywhere"

```rust
use stillwater::prelude::*;

#[derive(Clone)]
struct Config {
    timeout: u64,
    retries: u32,
}

// Functions don't need explicit config parameters
fn fetch_data() -> impl Effect<Output = String, Error = String, Env = Config> {
    // Ask for config when needed
    asks(|cfg: &Config| format!("Fetching with timeout={}", cfg.timeout))
}

fn fetch_with_extended_timeout() -> impl Effect<Output = String, Error = String, Env = Config> {
    // Temporarily modify environment for specific operations
    local(
        |cfg: &Config| Config { timeout: cfg.timeout * 2, ..*cfg },
        fetch_data()
    )
}

let config = Config { timeout: 30, retries: 3 };
let result = fetch_with_extended_timeout().run(&config).await?;
// Uses timeout=60 without changing the original config
```

### 7. "I need guaranteed cleanup even when errors occur"

```rust
use stillwater::effect::bracket::{bracket, bracket2, acquiring, BracketError};
use stillwater::prelude::*;

// Single resource with guaranteed cleanup
let result = bracket(
    open_connection(),                           // Acquire
    |conn| async move { conn.close().await },    // Release (always runs)
    |conn| fetch_user(conn, user_id),            // Use
).run(&env).await;

// Multiple resources with LIFO cleanup order
let result = bracket2(
    open_database(),
    open_file(path),
    |db| async move { db.close().await },        // Released second
    |file| async move { file.close().await },    // Released first (LIFO)
    |db, file| process(db, file),
).run(&env).await;

// Fluent builder for ergonomic multi-resource management
let result = acquiring(open_conn(), |c| async move { c.close().await })
    .and(open_file(), |f| async move { f.close().await })
    .and(acquire_lock(), |l| async move { l.release().await })
    .with_flat3(|conn, file, lock| do_work(conn, file, lock))
    .run(&env)
    .await;

// Explicit error handling with BracketError
let result = bracket_full(acquire, release, use_fn).run(&env).await;
match result {
    Ok(value) => println!("Success"),
    Err(BracketError::AcquireError(e)) => println!("Acquire failed"),
    Err(BracketError::UseError(e)) => println!("Use failed, cleanup succeeded"),
    Err(BracketError::CleanupError(e)) => println!("Use succeeded, cleanup failed"),
    Err(BracketError::Both { use_error, cleanup_error }) => println!("Both failed"),
}
```

### 8. "Retry logic is scattered and hard to test"

```rust
use stillwater::{Effect, RetryPolicy};
use std::time::Duration;

// Stillwater: Policy as Data
// Define retry policies as pure, testable values
let api_policy = RetryPolicy::exponential(Duration::from_millis(100))
    .with_max_retries(5)
    .with_max_delay(Duration::from_secs(2))
    .with_jitter(0.25);

// Test the policy without any I/O
assert_eq!(api_policy.delay_for_attempt(0), Some(Duration::from_millis(100)));
assert_eq!(api_policy.delay_for_attempt(1), Some(Duration::from_millis(200)));
assert_eq!(api_policy.delay_for_attempt(2), Some(Duration::from_millis(400)));

// Reuse the same policy across different effects
Effect::retry(|| fetch_user(id), api_policy.clone());
Effect::retry(|| save_order(order), api_policy.clone());

// Conditional retry: only retry transient failures
Effect::retry_if(
    || api_call(),
    api_policy,
    |err| matches!(err, ApiError::Timeout | ApiError::ServerError(_))
);

// Observability: hook into retry events for logging/metrics
Effect::retry_with_hooks(
    || api_call(),
    policy,
    |event| log::warn!(
        "Attempt {} failed: {}, retrying in {:?}",
        event.attempt, event.error, event.next_delay
    )
);
```

### 9. "I need to accumulate logs/metrics without threading state everywhere"

```rust
use stillwater::effect::writer::prelude::*;
use stillwater::effect::prelude::*;

// Without Writer: manually threading state
fn process(x: i32, logs: &mut Vec<String>) -> Result<i32, Error> {
    logs.push("Starting".into());
    let y = step1(x, logs)?;
    logs.push(format!("Step 1: {}", y));
    Ok(y)
}

// With Writer Effect: automatic accumulation
fn process_with_writer(x: i32) -> impl WriterEffect<
    Output = i32, Error = String, Env = (), Writes = Vec<String>
> {
    tell_one::<_, String, ()>("Starting".to_string())
        .and_then(move |_| into_writer::<_, _, Vec<String>>(pure::<_, String, ()>(x * 2)))
        .tap_tell(|y| vec![format!("Step 1: {}", y)])
}

// Run and get both result and accumulated logs
let (result, logs) = process_with_writer(21).run_writer(&()).await;
assert_eq!(result, Ok(42));
assert_eq!(logs, vec!["Starting", "Step 1: 42"]);

// Use any Monoid for accumulation - not just Vec!
use stillwater::monoid::Sum;

// Count operations
let effect = tell::<Sum<u32>, String, ()>(Sum(1))
    .and_then(|_| tell(Sum(1)))
    .and_then(|_| tell(Sum(1)));

let (_, Sum(count)) = effect.run_writer(&()).await;
assert_eq!(count, 3);
```

### 10. "I want the type system to prevent resource leaks"

```rust
use stillwater::effect::resource::*;

// Mark effects with resource acquisition at the TYPE level
fn open_file(path: &str) -> impl ResourceEffect<Acquires = Has<FileRes>> {
    pure(FileHandle::new(path)).acquires::<FileRes>()
}

fn close_file(handle: FileHandle) -> impl ResourceEffect<Releases = Has<FileRes>> {
    pure(()).releases::<FileRes>()
}

// The bracket pattern guarantees resource neutrality
// Use the builder for ergonomic syntax (single type parameter)
fn read_file_safe(path: &str) -> impl ResourceEffect<Acquires = Empty, Releases = Empty> {
    bracket::<FileRes>()
        .acquire(open_file(path))
        .release(|h| async move { close_file(h).run(&()).await })
        .use_fn(|h| read_contents(h))
}

// Transaction protocols enforced at compile time
fn begin_tx() -> impl ResourceEffect<Acquires = Has<TxRes>> { /* ... */ }
fn commit(tx: Tx) -> impl ResourceEffect<Releases = Has<TxRes>> { /* ... */ }

// This function MUST be resource-neutral or it won't compile
fn transfer_funds() -> impl ResourceEffect<Acquires = Empty, Releases = Empty> {
    bracket::<TxRes>()
        .acquire(begin_tx())
        .release(|tx| async move { commit(tx).run(&()).await })
        .use_fn(|tx| execute_queries(tx))
}
// Zero runtime overhead - all tracking is compile-time only!
```

## Core Features

- **`Validation<T, E>`** - Accumulate all errors instead of short-circuiting
- **Predicate combinators** - Composable validation logic with `and`, `or`, `not`, `all_of`, `any_of`
  - String predicates: `len_between`, `contains`, `starts_with`, `all_chars`, etc.
  - Number predicates: `between`, `gt`, `lt`, `positive`, `negative`, etc.
  - Collection predicates: `all`, `any`, `has_len`, `is_empty`, etc.
  - Seamless integration with `Validation` via `ensure()` and `validate()`
- **Validation combinators** - Declarative validation with `ensure` family (replaces verbose `and_then` boilerplate)
  - `Effect`: `.ensure()`, `.ensure_with()`, `.ensure_pred()`, `.unless()`, `.filter_or()`
  - `Validation`: `.ensure()`, `.ensure_fn()`, `.ensure_with()`, `.ensure_fn_with()`, `.unless()`, `.filter_or()`
  - Zero-cost: compiles to concrete types with no heap allocation
  - Reduces 12-line validation blocks to single-line predicates
- **Refined types** - "Parse, don't validate" pattern for type-level invariants
  - `Refined<T, P>` wrapper guarantees value satisfies predicate P at compile time
  - Numeric predicates: `Positive`, `NonNegative`, `Negative`, `NonZero`, `InRange<MIN, MAX>`
  - String predicates: `NonEmpty`, `Trimmed`, `MaxLength<N>`, `MinLength<N>`
  - Collection predicates: `NonEmpty`, `MaxSize<N>`, `MinSize<N>` for `Vec<T>`
  - Combinators: `And`, `Or`, `Not` for composing complex predicates
  - Type aliases: `NonEmptyString`, `PositiveI32`, `Port`, `Percentage`, etc.
  - Validation integration: `validate()`, `validate_vec()`, `with_field()` for error accumulation
  - Zero-cost: same memory layout as inner type, predicate is compile-time only
- **`NonEmptyVec<T>`** - Type-safe non-empty collections with guaranteed head element
- **`Effect` trait** - Zero-cost effect composition following the `futures` crate pattern
  - Zero heap allocations by default
  - Explicit `.boxed()` when type erasure is needed
  - Returns `impl Effect` for optimal performance
- **Zip combinators** - Combine independent effects into tuples
  - `zip()`, `zip_with()` methods for pairwise combination
  - `zip3()` through `zip8()` for flat tuple results
  - Zero-cost: all combinators return concrete types
- **Parallel effect execution** - Run independent effects concurrently
  - Zero-cost: `par2()`, `par3()`, `par4()` for heterogeneous effects
  - Boxed: `par_all()`, `par_try_all()`, `race()`, `par_all_limit()` for homogeneous collections
- **Retry and resilience** - Policy-as-data approach with exponential, linear, constant, and Fibonacci backoff. Includes jitter, conditional retry, retry hooks, and timeout support
- **Error recovery** - Selective error handling with predicate-based recovery
  - `recover()`, `recover_with()`, `recover_some()` for conditional error recovery
  - `fallback()`, `fallback_to()` for default values and alternative effects
  - Predicate composition for sophisticated recovery strategies
  - Real-world patterns: multi-tier caching, graceful degradation, API fallback
- **Resource management** - Comprehensive bracket pattern for safe acquire/use/release
  - `bracket()`, `bracket2()`, `bracket3()` for single and multiple resources with LIFO cleanup
  - `bracket_full()` returns `BracketError` with explicit error handling for all failure modes
  - `acquiring()` builder for fluent multi-resource management with `with_flat2/3/4`
  - Guaranteed cleanup even on errors, partial acquisition rollback
- **Compile-time resource tracking** - Type-level resource safety with zero runtime overhead
  - Resource markers: `FileRes`, `DbRes`, `LockRes`, `TxRes`, `SocketRes` (or define custom)
  - `ResourceEffect` trait with `Acquires`/`Releases` associated types
  - Extension methods: `.acquires::<R>()`, `.releases::<R>()`, `.neutral()`
  - `bracket::<R>()` builder for ergonomic resource brackets (single type parameter)
  - `resource_bracket` function for guaranteed resource-neutral operations
  - `assert_resource_neutral` for compile-time leak detection
- **Traverse and sequence** - Transform collections with `traverse()` and `sequence()` for both validations and effects
- **Reader pattern helpers** - Clean dependency injection with `ask()`, `asks()`, and `local()`
- **Writer Effect** - Accumulate logs, metrics, or audit trails alongside computation
  - `tell()`, `tell_one()` for emitting values to accumulator
  - `tap_tell()` for logging derived values after success
  - `censor()` for filtering/transforming accumulated writes
  - `listen()`, `pass()` for introspecting and controlling writes
  - `traverse_writer()`, `fold_writer()` for collection operations
  - Works with any `Monoid`: `Vec`, `Sum`, `Product`, custom types
- **`Semigroup` trait** - Associative combination of values
  - Extended implementations for `HashMap`, `HashSet`, `BTreeMap`, `BTreeSet`, `Option`
  - Wrapper types: `First`, `Last`, `Intersection` for alternative semantics
- **`Monoid` trait** - Identity elements for powerful composition patterns
- **Testing utilities** - Ergonomic test helpers
  - `MockEnv` builder for composing test environments
  - Assertion macros: `assert_success!`, `assert_failure!`, `assert_validation_errors!`
  - `TestEffect` wrapper for deterministic effect testing
  - Optional `proptest` feature for property-based testing
- **Context chaining** - Never lose error context
- **Tracing integration** - Instrument effects with semantic spans using the standard `tracing` crate
- **Zero-cost abstractions** - Follows `futures` crate pattern: concrete types, no allocation by default
- **Works with `?` operator** - Integrates with Rust idioms
- **No heavy macros** - Clear types, obvious behavior

## Quick Start

```rust
use stillwater::prelude::*;

// 1. Validation with error accumulation
fn validate_user(input: UserInput) -> Validation<User, Vec<Error>> {
    Validation::all((
        validate_email(&input.email),
        validate_age(input.age),
        validate_name(&input.name),
    ))
    .map(|(email, age, name)| User { email, age, name })
}

// 2. Effect composition (zero-cost by default)
fn create_user(input: UserInput) -> impl Effect<Output = User, Error = AppError, Env = AppEnv> {
    // Validate (pure, accumulates errors)
    from_validation(validate_user(input).map_err(AppError::Validation))
        // Check if exists (I/O)
        .and_then(|user| {
            from_fn(move |env: &AppEnv| env.db.find_by_email(&user.email))
                .and_then(move |existing| {
                    if existing.is_some() {
                        fail(AppError::EmailExists)
                    } else {
                        pure(user)
                    }
                })
        })
        // Save user (I/O)
        .and_then(|user| {
            from_fn(move |env: &AppEnv| env.db.insert_user(&user))
                .map(move |_| user)
        })
        .context("Creating new user")
}

// 3. Run at application boundary
let env = AppEnv { db, cache, logger };
let result = create_user(input).run(&env).await?;
```

## Zero-Cost Effect System

Version 0.11.0 introduces a zero-cost effect system following the `futures` crate pattern:

```rust
// Free-standing constructors (not methods)
let effect = pure(42);                    // Not Effect::pure(42)
let effect = fail("error");               // Not Effect::fail("error")
let effect = from_fn(|env| Ok(env.value)); // Not Effect::from_fn(...)

// Chain combinators - each returns a concrete type, zero allocations
let result = pure(1)
    .map(|x| x + 1)
    .and_then(|x| pure(x * 2))
    .map(|x| x.to_string());

// Use .boxed() when you need type erasure
fn dynamic_effect(flag: bool) -> BoxedEffect<i32, String, ()> {
    if flag {
        pure(1).boxed()
    } else {
        pure(2).boxed()
    }
}

// Collections of effects require boxing
let effects: Vec<BoxedEffect<i32, String, Env>> = vec![
    effect1.boxed(),
    effect2.boxed(),
];
let results = par_all(effects, &env).await;
```

**When to use `.boxed()`:**
- Storing effects in collections (`Vec<BoxedEffect<...>>`)
- Returning different effect types from branches
- Recursive effect definitions
- Dynamic dispatch scenarios

**When NOT to use `.boxed()`:**
- Simple linear chains (use `impl Effect`)
- Fixed combinator sequences
- Performance-critical paths

## 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 by default (follows `futures` crate pattern)
- 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 via `Try` trait
- Zero-cost via concrete types and monomorphization (like `futures`)
- Integrates with async/await
- Borrows checker friendly
- Clear error messages

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
stillwater = "1.0"

# Optional: async support
stillwater = { version = "0.11", features = ["async"] }

# Optional: tracing integration
stillwater = { version = "0.11", features = ["tracing"] }

# Optional: jitter for retry policies
stillwater = { version = "0.11", features = ["jitter"] }

# Optional: property-based testing
stillwater = { version = "0.11", features = ["proptest"] }

# Multiple features
stillwater = { version = "0.11", features = ["async", "tracing", "jitter"] }
```

## Examples

Run any example with `cargo run --example <name>`:

| Example | Demonstrates |
|---------|--------------|
| [predicates]examples/predicates.rs | Composable predicate combinators for validation logic |
| [form_validation]examples/form_validation.rs | Validation error accumulation |
| [homogeneous_validation]examples/homogeneous_validation.rs | Type-safe validation for discriminated unions before combining |
| [nonempty]examples/nonempty.rs | NonEmptyVec type for guaranteed non-empty collections |
| [user_registration]examples/user_registration.rs | Effect composition and I/O separation |
| [error_context]examples/error_context.rs | Error trails for debugging |
| [data_pipeline]examples/data_pipeline.rs | Real-world ETL pipeline |
| [testing_patterns]examples/testing_patterns.rs | Testing pure vs effectful code |
| [reader_pattern]examples/reader_pattern.rs | Reader pattern with ask(), asks(), and local() |
| [writer_logging]examples/writer_logging.rs | Writer Effect for accumulating logs, metrics, and audit trails |
| [validation]examples/validation.rs | Validation type and error accumulation patterns |
| [effects]examples/effects.rs | Effect type and composition patterns |
| [parallel_effects]examples/parallel_effects.rs | Parallel execution with par_all, race, and par_all_limit |
| [recover_patterns]examples/recover_patterns.rs | Error recovery with recover, recover_with, recover_some, fallback patterns |
| [retry_patterns]examples/retry_patterns.rs | Retry policies, backoff strategies, timeouts, and resilience patterns |
| [io_patterns]examples/io_patterns.rs | IO module helpers for reading/writing |
| [pipeline]examples/pipeline.rs | Data transformation pipelines |
| [traverse]examples/traverse.rs | Traverse and sequence for collections of validations and effects |
| [monoid]examples/monoid.rs | Monoid and Semigroup traits for composition |
| [extended_semigroup]examples/extended_semigroup.rs | Semigroup for HashMap, HashSet, Option, and wrapper types |
| [tracing_demo]examples/tracing_demo.rs | Tracing integration with semantic spans and context |
| [boxing_decisions]examples/boxing_decisions.rs | When to use `.boxed()` vs zero-cost effects |
| [resource_scopes]examples/resource_scopes.rs | Bracket pattern for safe resource management with guaranteed cleanup |
| [resource_tracking]examples/resource_tracking.rs | Compile-time resource tracking with type-level safety |
| [refined]examples/refined.rs | Refined types for "parse, don't validate" pattern with type-level invariants |

See [examples/](examples/) directory for full code.

## Production Readiness

**Status: 0.11.0 - Production Ready**

- 355 unit tests passing
- 113 documentation tests passing
- 21 runnable examples
- Zero clippy warnings
- 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.

## Migration from 0.10.x

Version 0.11.0 introduces breaking changes with the zero-cost effect system. See [MIGRATION.md](docs/MIGRATION.md) for detailed upgrade instructions.

**Key changes:**
```rust
// Before (0.10.x)
Effect::pure(x)
Effect::fail(e)
Effect::from_fn(f)

// After (0.11.0)
pure(x)
fail(e)
from_fn(f)

// Return types changed
fn old() -> Effect<T, E, Env> { ... }      // Boxed by default
fn new() -> impl Effect<...> { ... }        // Zero-cost by default
fn boxed() -> BoxedEffect<T, E, Env> { ... } // Explicit boxing
```

## Documentation

- [User Guide]docs/guide/README.md - Comprehensive tutorials
- [API Docs]https://docs.rs/stillwater - Full API reference
- [FAQ]docs/FAQ.md - Common questions
- [Design]DESIGN.md - Architecture and decisions
- [Philosophy]PHILOSOPHY.md - Core principles
- [Patterns]docs/PATTERNS.md - Common patterns and recipes
- [Comparison]docs/COMPARISON.md - vs other libraries
- [Migration Guide]docs/MIGRATION.md - Upgrading from 0.10.x to 0.11.0

## Migrating from Result

Already using `Result` everywhere? No problem! Stillwater integrates seamlessly:

```rust
// Your existing code works as-is
fn validate_email(email: &str) -> Result<Email, Error> {
    // ...
}

// Upgrade to accumulation when you need it
fn validate_form(input: FormInput) -> Validation<Form, Vec<Error>> {
    Validation::all((
        Validation::from_result(validate_email(&input.email)),
        Validation::from_result(validate_age(input.age)),
    ))
}

// Convert back to Result when needed
let result: Result<Form, Vec<Error>> = 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]https://github.com/iepathos/stillwater/issues
- Documentation improvements
- More examples and use cases
- API feedback and design discussions

Before submitting PRs, please open an issue to discuss the change.

## Ecosystem

Stillwater is part of a family of libraries that share the same functional programming philosophy:

| Library | Description |
|---------|-------------|
| [premortem]https://github.com/iepathos/premortem | Configuration validation that finds all errors before your app runs. Multi-source loading with error accumulation and value origin tracing. |
| [postmortem]https://github.com/iepathos/postmortem | Validation library that accumulates all errors with precise JSON path tracking. Composable schemas, cross-field validation, and effect integration. |
| [mindset]https://github.com/iepathos/mindset | Zero-cost, effect-based state machines. Pure guards for validation, explicit actions for side effects, environment pattern for testability. |

All libraries emphasize:
- Error accumulation over short-circuiting
- Pure core, effects at the boundaries
- Zero-cost abstractions
- Testability through dependency injection

## 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."*