Creating Effects — succeed, fail, and pure
Every effect starts as either a success or a failure. The two constructors that express this are succeed and fail.
succeed
succeed wraps a value into an effect that, when run, immediately produces that value:
#![allow(unused)] fn main() { use id_effect::{Effect, succeed}; let answer: Effect<i32, String, ()> = succeed(42); let greeting: Effect<String, String, ()> = succeed("Hello, world!".to_string()); }
Nothing happens when you call succeed. You get back a description — a lazy recipe that says "produce this value when someone asks." The 42 is already there, but no computation has been executed.
The type parameters are important:
A = i32— the value we produceE = String— the error type (unused here, but we still have to pick one)R = ()— no environment needed
If you prefer the FP vocabulary, pure is an alias for succeed:
#![allow(unused)] fn main() { use id_effect::pure; let effect = pure(42_i32); }
Both names refer to exactly the same thing. Use whichever feels natural in context.
fail
fail wraps an error into an effect that, when run, immediately fails with that error:
#![allow(unused)] fn main() { use id_effect::{Effect, fail}; let oops: Effect<i32, String, ()> = fail("something went wrong".to_string()); }
Again, nothing executes. oops is a description of a failure, not the failure itself. You can pass it around, store it, and transform it without triggering any error handling.
The type annotation matters: Effect<i32, String, ()> says this would have produced an i32 on success — we just know it won't.
From a Closure
For cases where you want to capture some computation in an effect (but still defer it):
#![allow(unused)] fn main() { use id_effect::{Effect, effect}; let computed: Effect<i32, String, ()> = effect!(|_r: &mut ()| { let x = expensive_calculation(); x * 2 }); }
The body of effect! runs lazily — only when the effect is executed. This is the workhorse macro we'll cover thoroughly in Chapter 3.
Type Inference
Rust's type inference often lets you skip the annotations:
#![allow(unused)] fn main() { // Types inferred from usage let answer = succeed(42); // Effect<i32, _, ()> let greeting = succeed("hi"); // Effect<&str, _, ()> }
The error type E is usually inferred from how the effect is used later — when you chain it with other effects that can fail, the error type propagates. You'll only need to annotate explicitly when the compiler asks.
Quick Reference
#![allow(unused)] fn main() { succeed(value) // Effect that produces value pure(value) // Alias for succeed fail(error) // Effect that fails with error effect!(|_r| { … }) // Effect from a lazy closure }
These three constructors cover every starting point. Everything else is transformation and composition.