id_effect 0.2.0

Effect<A, E, R> (sync + async), context/layers, pipe — interpreter-style, no bundled executor
Documentation
# Stm and commit — Building Transactions

The `stm!` macro produces `Stm<A>` values — descriptions of transactional computations. To execute them, you use `commit` or `atomically`.

## commit: Lift Stm into Effect

```rust
use id_effect::{commit, Stm, Effect};

let transaction: Stm<i32> = stm! {
    let a = ~ ref_a.read_stm();
    let b = ~ ref_b.read_stm();
    a + b
};

// Lift into Effect
let effect: Effect<i32, Never, ()> = commit(transaction);

// Now run it
let result = run_blocking(effect)?;
```

`commit` wraps a `Stm` in an effect that, when run, executes the transaction and retries if there's a conflict. The `E` type of `commit(stm)` is `Never` unless the `Stm` can fail (see `stm::fail`).

## atomically: Direct Execution

```rust
use id_effect::atomically;

// Run a transaction immediately in the current context
let value: i32 = atomically(stm! {
    ~ counter.modify_stm(|n| n + 1);
    ~ counter.read_stm()
});
```

`atomically` is the synchronous equivalent of `commit` + `run_blocking`. Use it when you're already outside the effect system and need a quick transactional update.

## stm::fail: Transactional Errors

Transactions can fail with typed errors:

```rust
use id_effect::stm;

fn withdraw(account: &TRef<u64>, amount: u64) -> Stm<u64> {
    stm! {
        let balance = ~ account.read_stm();
        if balance < amount {
            ~ stm::fail(InsufficientFunds);  // abort the transaction
        }
        ~ account.write_stm(balance - amount);
        balance - amount
    }
}

// commit propagates the error into E
let effect: Effect<u64, InsufficientFunds, ()> = commit(withdraw(&account, 100));
```

`stm::fail(e)` aborts the current transaction with error `e`. The transaction is *not* retried — it fails immediately with the given error.

## stm::retry: Block Until Condition

Sometimes a transaction should wait until a condition is true rather than failing:

```rust
// Block (retry) until the queue has items
fn dequeue(queue: &TRef<Vec<Item>>) -> Stm<Item> {
    stm! {
        let items = ~ queue.read_stm();
        if items.is_empty() {
            ~ stm::retry();  // block until queue changes, then retry
        }
        let item = items[0].clone();
        ~ queue.write_stm(items[1..].to_vec());
        item
    }
}
```

`stm::retry()` doesn't mean "try again immediately." It means "block until any `TRef` I read has changed, then try again." This is how `TQueue` implements blocking dequeue without busy-waiting.

## Composing Transactions

Transactions compose by sequencing `stm!` blocks:

```rust
let big_transaction: Stm<()> = stm! {
    // Sub-transaction 1
    let _ = ~ transfer_funds(&from, &to, amount);
    // Sub-transaction 2
    let _ = ~ record_audit_log(&from, &to, amount);
    ()
};

// Both operations commit atomically or neither does
let effect = commit(big_transaction);
```

The composed transaction retries as a unit — if either sub-operation sees a conflict, the whole thing restarts from the beginning.