The ~ Operator Explained
The ~ (tilde) is the bind operator inside effect!. It means: "execute this effect and give me its success value; if it fails, propagate the failure and stop."
Basic Usage
#![allow(unused)] fn main() { effect! { let user = ~ fetch_user(42); // bind the result to `user` user.name } }
~ fetch_user(42) desugars to a flat_map. The rest of the block becomes the body of the closure.
Discarding Results
When you don't need the value, use ~ without a binding:
#![allow(unused)] fn main() { effect! { ~ log_event("processing started"); // run for side effect, discard result let result = ~ do_work(); ~ log_event("processing done"); result } }
Both ~ log_event(...) expressions run for their effects and the () return is discarded.
Method Calls on Effects
~ works on any expression that evaluates to an Effect. That includes method chains:
#![allow(unused)] fn main() { effect! { let user = ~ fetch_user(id).map_error(AppError::Database); let posts = ~ fetch_posts(user.id) .map_error(AppError::Database) .retry(Schedule::exponential(100.ms()).take(3)); (user, posts) } }
The ~ applies to the entire expression, including any .map_error(), .retry(), etc. that follow.
~ in Conditionals and Loops
You can use ~ inside if expressions and loops:
#![allow(unused)] fn main() { effect! { let value = if condition { ~ compute_a() } else { ~ compute_b() }; process(value) } }
Both branches are effects; the macro handles either path.
#![allow(unused)] fn main() { effect! { for id in user_ids { ~ process_user(id); // sequential: one at a time } "done" } }
Note: this is sequential iteration. For concurrent processing, use fiber_all (Chapter 9).
What ~ Cannot Do
~ only works inside an effect! block. Calling it outside is a compile error:
#![allow(unused)] fn main() { // Does not compile — ~ is not valid here let x = ~ fetch_user(42); // Must be inside effect! let x = effect! { ~ fetch_user(42) }; }
Also, ~ cannot bind across an async closure boundary. If you're calling from_async, the body of the async block is separate:
#![allow(unused)] fn main() { effect! { let result = ~ from_async(|_r| async move { // Inside here, you're in regular Rust async — no ~ let data = some_future().await?; Ok(data) }); result } }
Use ~ outside the async move block; use .await inside it.
The Old Postfix Syntax (Deprecated)
Early versions of id_effect used a postfix tilde: expr ~. This is no longer valid. Always use the prefix form:
#![allow(unused)] fn main() { // OLD — do not use step_a() ~; // GOOD ~ step_a(); let x = ~ step_b(); }
If you see postfix tilde in older code, update it to the prefix form.