Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
YouTube Security VideosfreeCodeCamp.org: TimescaleDB Course – PostgreSQL for Time-Series Data(23.09.2026 um 12:30 Uhr)
Windows Tipps & SecurityAndroid 17: Rollout auf Samsung-Galaxy-Smartphones verzögert sich(23.09.2026 um 11:42 Uhr)
Unix & Linux ServerUSN-8733-2: Gzip vulnerabilities(22.09.2026 um 18:04 Uhr)
Sichere ProgrammierungHow to Build Custom PowerPoint Add-Ins for Enterprise Teams(23.09.2026 um 11:25 Uhr)
Sichere ProgrammierungSearch Google Jobs in Real-Time with Go and SerpApi 🚀(23.09.2026 um 12:13 Uhr)
Sichere ProgrammierungA Psychological State is a Coefficient Vector(23.09.2026 um 12:16 Uhr)
YouTube Security VideosfreeCodeCamp.org: TimescaleDB Course – PostgreSQL for Time-Series Data(23.09.2026 um 12:30 Uhr)
Windows Tipps & SecurityAndroid 17: Rollout auf Samsung-Galaxy-Smartphones verzögert sich(23.09.2026 um 11:42 Uhr)
Unix & Linux ServerUSN-8733-2: Gzip vulnerabilities(22.09.2026 um 18:04 Uhr)
Sichere ProgrammierungHow to Build Custom PowerPoint Add-Ins for Enterprise Teams(23.09.2026 um 11:25 Uhr)
Sichere ProgrammierungSearch Google Jobs in Real-Time with Go and SerpApi 🚀(23.09.2026 um 12:13 Uhr)
Sichere ProgrammierungA Psychological State is a Coefficient Vector(23.09.2026 um 12:16 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Stillwater 1.0: Pragmatic Effect Composition and Validation for Rust

Originally published on Entropic Drift I'm proud to announce the release of stillwater 1.0, a production-ready Rust library for pragmatic effect composition and validation. What is Stillwater? Stillwater implements the pure…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Originally published on Entropic Drift






I'm proud to announce the release of stillwater 1.0, a production-ready Rust library for pragmatic effect composition and validation.






What is Stillwater?



Stillwater implements the pure core, imperative shell pattern for Rust. The name is a mental model: like a still pond with streams flowing through it, your business logic stays calm and predictable at the center while effects (I/O, side effects) flow at the boundaries.




       Still Waters
╱ ╲
Pure Logic Effects
↓ ↓
Unchanging Flowing
Predictable Performing I/O
Testable At boundaries






This isn't Haskell-in-Rust. It's an attempt at better Rust, leveraging pragmatic functional patterns where they make code more testable and maintainable, while respecting Rust's ownership model and idioms.






What Are Effects?



If you're coming from OOP, "effects" might be unfamiliar. The concept is simple:



Typical Rust functions execute immediately:




fn add(a: i32, b: i32) -> i32 {
a + b // eager evaluation, happens NOW, returns result
}






An effect is a description of what to do, executed later:




fn fetch_user(id: UserId) -> impl Effect<Output = User, Error = DbError, Env = AppEnv> {
asks(|env| env.db.get_user(id)) // describes the operation, deferred evaluation
}

// Nothing happens yet - we just have a description
let effect = fetch_user(42);

// NOW it executes
let user = effect.run(&env).await;






This separation lets you:





  • Compose descriptions before running them


  • Test logic without executing I/O


  • Control when and where side effects actually happen



If you've used async Rust, you already know this pattern - async fn returns a Future (a description), which runs when you .await it. Effects are the same idea, extended with environment access, typed errors, and composition tools.






The Core Problem



Most code mixes business logic with I/O:




fn process_user(id: UserId, db: &Database) -> Result<User, Error> {
let user = db.fetch_user(id)?; // I/O
if user.age < 18 { // Logic
return Err(Error::TooYoung);
}
let discount = if user.premium { 0.15 } else { 0.05 }; // Logic
db.save_user(&user)?; // I/O
Ok(user)
}






Testing this requires mocking the database. Reasoning about it requires mentally separating what transforms data from what performs I/O. Reusing the discount logic means extracting it anyway.



Stillwater separates these concerns by design, using multiple features working together:




use stillwater::prelude::*;
use stillwater::refined::{Refined, NonEmpty, InRange};
use stillwater::predicate::*;

// Refined types - validity proven at compile time
type Username = Refined<String, And<NonEmpty, MaxLength<20>>>;
type Age = Refined<u8, InRange<18, 120>>;
type Email = Refined<String, NonEmpty>; // simplified
// See https://github.com/iepathos/stilltypes for full Email type implementation

// Pure validation - accumulates ALL errors at once
fn validate_registration(input: RawInput) -> Validation<ValidUser, Vec<String>> {
Validation::all((
Username::new(input.username)
.map_err(|_| vec!["Username must be 1-20 characters".into()]),
Age::new(input.age)
.map_err(|_| vec!["Age must be 18-120".into()]),
Email::new(input.email)
.map_err(|_| vec!["Email required".into()]),
))
.map(|(username, age, email)| ValidUser { username, age, email })
}

// Pure business logic - no I/O, easily testable
fn calculate_discount(user: &ValidUser) -> Discount {
if user.age.get() >= 65 { Discount(0.20) } // Senior discount
else { Discount(0.05) }
}

// Effect composition - describes I/O, doesn't execute it
fn register_user(input: RawInput) -> impl Effect<Output = User, Error = AppError, Env = AppEnv> {
// Validate (pure) -> transform to effect
from_validation(validate_registration(input))
.map_err(|errs| AppError::Validation(errs))
// Apply pure business logic
.map(|valid| {
let discount = calculate_discount(&valid);
(valid, discount)
})
// Describe the I/O - asks() doesn't execute, it builds a description
.and_then(|(valid, discount)| {
asks(move |env: &AppEnv| env.db.create_user(&valid, discount))
})
.context("registering new user")
}

// I/O executes at the application boundary
async fn handle_registration(input: RawInput, env: &AppEnv) -> Result<User, AppError> {
register_user(input).run(env).await // <-- I/O happens here
}

// In tests - pure functions need no mocks
#[test]
fn senior_gets_20_percent_discount() {
let user = ValidUser {
username: Username::new("alice".into()).unwrap(),
age: Age::new(70).unwrap(), // 70 years old
email: Email::new("[email protected]".into()).unwrap(),
};
assert_eq!(calculate_discount(&user), Discount(0.20));
}






The key insight: register_user returns an effect description, not a result. No I/O executes until .run(env) is called. This keeps your business logic pure and testable - the register_user function itself does no I/O, it just describes what I/O should happen.



This example showcases several stillwater features:





  • Refined types (Username, Age, Email) encode invariants in the type system - once constructed, they're guaranteed valid


  • Validation accumulates all errors - users see every problem at once, not one at a time


  • Pure functions (calculate_discount) are trivially testable with no mocks


  • Deferred I/O - asks() describes database operations; actual I/O happens only at .run()


  • Context chaining preserves error trails for debugging






Key Features in 1.0






Zero-Cost Effect System



Following the futures crate pattern, effects are zero-cost by default with opt-in boxing:




use stillwater::prelude::*;

// No heap allocation - each combinator returns a concrete type
let effect = pure::<_, String, ()>(42)
.map(|x| x + 1)
.and_then(|x| pure(x * 2));

// Type: AndThen<Map<Pure<i32, ...>, ...>, ...>
// Compiler inlines everything - zero allocation!






Use .boxed() only when you need type erasure - like match arms with different effect types:




// Different branches produce different concrete types
fn fetch_user(id: UserId, use_cache: bool) -> BoxedEffect<User, Error, Env> {
if use_cache {
asks(|env: &Env| env.cache.get(id)) // type A
.boxed()
} else {
asks(|env: &Env| env.db.fetch(id)) // type B
.and_then(|user| cache_user(user))
.boxed()
}
}






If you've worked with async Rust, you already understand the pattern.






Validation with Error Accumulation



Rust's Result short-circuits on the first error. Stillwater's Validation accumulates all of them:




use stillwater::Validation;

fn validate_registration(input: RawInput) -> Validation<User, Vec<String>> {
Validation::all((
validate_email(input.email),
validate_age(input.age),
validate_username(input.username),
))
.map(|(email, age, username)| User { email, age, username })
}

// Returns ALL errors at once:
// Failure(["Invalid email format", "Age must be 18+", "Username too short"])






No more frustrating round-trips where users fix one error only to discover another.






Predicate Combinators



Composable, reusable predicates for declarative validation:




use stillwater::predicate::*;

let valid_username = len_between(3, 20)
.and(all_chars(|c| c.is_alphanumeric() || c == '_'));

let valid_port = between(1024, 65535);

// Combine with validation
Validation::success(username)
.ensure(valid_username, "Invalid username format")









Refined Types



The "parse, don't validate" pattern - type-level invariants that guarantee validity:




use stillwater::refined::{Refined, NonEmpty, Positive, InRange};

type NonEmptyString = Refined<String, NonEmpty>;
type PositiveI32 = Refined<i32, Positive>;
type Port = Refined<u16, InRange<1024, 65535>>;

// Validate at boundaries
let name = NonEmptyString::new("Alice".to_string())?;
let port = Port::new(8080)?;

// Use freely inside - validity guaranteed by construction
fn process_user(name: NonEmptyString, port: Port) {
// No need to re-check: types prove validity
}






Zero runtime overhead - same memory layout as the inner type.






Bracket Pattern for Resource Management



Guaranteed acquire/use/release semantics:




use stillwater::effect::bracket::*;

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;

// Fluent builder for multiple resources
let result = acquiring(open_db(), |db| async move { db.close().await })
.and(open_file(), |f| async move { f.close().await })
.with_flat2(|db, file| process(db, file))
.run(&env)
.await;









Compile-Time Resource Tracking



Type-level resource tracking prevents leaks at compile time:




use stillwater::effect::resource::*;

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>()
}

// Bracket guarantees resource neutrality - won't compile if unbalanced!
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))
}






Zero runtime overhead - all tracking happens at compile time.






Retry Policies as Data



Policies are composable, testable values - not scattered implementation:




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

// Define policy as pure data
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)));

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

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









Writer Effect for Audit Trails



Accumulate logs without threading state through every function:




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

fn process_order(order: Order) -> impl WriterEffect<
Output = Receipt, Error = String, Env = (), Writes = Vec<String>
> {
tell_one("Processing order".to_string())
.and_then(move |_| validate_order(order))
.tap_tell(|_| vec!["Validation passed".to_string()])
.and_then(|order| charge_card(order))
.tap_tell(|receipt| vec![format!("Charged: ${}", receipt.amount)])
}

let (result, logs) = process_order(order).run_writer(&()).await;
// logs: ["Processing order", "Validation passed", "Charged: $42.00"]









Parallel Effect Execution



Run independent effects concurrently:




use stillwater::prelude::*;

// Combine independent effects
fn load_profile(id: UserId) -> impl Effect<Output = Profile, Error = AppError, Env = AppEnv> {
zip3(fetch_user(id), fetch_settings(id), fetch_preferences(id))
.map(|(user, settings, prefs)| Profile { user, settings, prefs })
}

// Homogeneous collection
let results = par_all(effects, &env).await;

// Race - first success wins
let result = race(fetch_from_primary(), fetch_from_backup(), &env).await;









Production Ready



1.0 represents a stable API ready for production use:




  • 355+ unit tests passing

  • 113+ documentation tests

  • 21 comprehensive runnable examples

  • Zero clippy warnings

  • Full async/await support

  • CI/CD with security audits






Philosophy



Stillwater is built on six core beliefs:





  1. Pure Core, Imperative Shell - Separate logic from I/O for testability


  2. Fail Completely, Not Partially - Accumulate all errors, not just the first


  3. Errors Tell Stories - Context chaining preserves error trails


  4. Composition Over Complexity - Build complex from simple pieces


  5. Types Guide, Don't Restrict - Pragmatic type system use


  6. Pragmatism Over Purity - Better Rust, not academic FP



We don't fight the borrow checker. We don't replace the standard library. We work with ?, integrate with async/await, and follow Rust idioms.






When to Use Stillwater



Good fit:




  • Complex form/config validation (error accumulation shines)

  • Business logic that needs extensive testing (pure core)

  • Deep call stacks needing error context

  • Long-lived codebases prioritizing maintainability

  • Effects with dependency injection (Reader pattern)

  • Resource management with guaranteed cleanup



Less suitable:




  • Simple CRUD applications (standard Result is fine)

  • Performance-critical hot paths (profile first)

  • Teams not aligned on functional patterns






The Ecosystem



Stillwater is part of a family of libraries sharing the same philosophy:




























Library Purpose
premortem Configuration validation and multi-source loading
postmortem JSON schema validation with precise path tracking
mindset Zero-cost effect-based state machines
stilltypes Domain-specific refined types





Getting Started



Add to your Cargo.toml:




[dependencies]
stillwater = "1.0"






Start with validation - it's the most immediately useful part:




use stillwater::Validation;

fn validate(input: &Input) -> Validation<Valid, Vec<String>> {
Validation::all((
validate_field1(&input.field1),
validate_field2(&input.field2),
validate_field3(&input.field3),
))
.map(|(f1, f2, f3)| Valid { f1, f2, f3 })
}






Then explore effects when you need testable I/O composition:




use stillwater::prelude::*;

fn fetch_and_process(id: Id) -> impl Effect<Output = Result, Error = AppError, Env = AppEnv> {
asks(|env: &AppEnv| env.api.fetch(id))
.and_then(|data| pure(transform(data)))
.context("fetching and processing data")
}

// In tests - no real API needed
let result = fetch_and_process(id).run(&mock_env).await;









What's Next



With 1.0 stable, future work focuses on:




  • More refined type predicates stilltypes

  • Additional effect combinators

  • Performance optimizations

  • Ecosystem integration examples






Check out stillwater on GitHub or crates.io.






Want more content like this? Follow me on Dev.to or subscribe to Entropic Drift for posts on AI-powered development workflows, Rust tooling, and technical debt management.



Check out my open-source projects:





  • Debtmap - Technical debt analyzer


  • Prodigy - AI workflow orchestration

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Stillwater 1.0: Pragmatic Effect Composition and Validation for Rust

Thematisch verwandte Begriffe: Stillwater, Pragmatic, Effect, Composition · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-96258 | A vulnerability has been found in onSite internet GmbH Auktion NG Auktio…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick