id_effect 0.1.0

Effect<A, E, R> (sync + async), context/layers, pipe — interpreter-style, no bundled executor
Documentation
# Service Traits — Defining Interfaces

The first step in defining a service is the trait — the contract between the service and its callers.

## Define the Interface

```rust
use id_effect::{Effect};

// The interface contract
trait UserRepository: Send + Sync {
    fn get_user(&self, id: u64) -> Effect<User, DbError, ()>;
    fn save_user(&self, user: &User) -> Effect<(), DbError, ()>;
    fn list_users(&self) -> Effect<Vec<User>, DbError, ()>;
}
```

A few conventions:
- Methods return `Effect<_, _, ()>` — the service itself has `R = ()` because it doesn't need additional context. The `R` of callers (who access the service through the environment) is what carries the requirement.
- `Send + Sync` on the trait means implementations can be stored in `Arc<dyn Trait>` and shared across fibers.
- Method names are verb-oriented: `get_user`, `save_user`, not `user` or `users`.

## Define the Tag

Each service needs a tag that identifies it in the environment:

```rust
use id_effect::service_key;

// Associates UserRepositoryTag with Arc<dyn UserRepository>
service_key!(UserRepositoryTag: Arc<dyn UserRepository>);
```

`service_key!` generates:
- A zero-sized `UserRepositoryTag` type
- A `Tag` implementation that maps `UserRepositoryTag``Arc<dyn UserRepository>`
- A `NeedsUserRepository` supertrait that functions can use in bounds

## Define the Needs Supertrait

```rust
use id_effect::{Get};

// Generated by service_key! or defined manually
pub trait NeedsUserRepository: Get<UserRepositoryTag> {}
impl<R: Get<UserRepositoryTag>> NeedsUserRepository for R {}
```

Now any function that uses the user repository declares this:

```rust
fn get_user_profile(id: u64) -> Effect<UserProfile, AppError, impl NeedsUserRepository> {
    effect! {
        let repo = ~ UserRepositoryTag;  // get the service
        let user = ~ repo.get_user(id);
        UserProfile::from(user)
    }
}
```

The compiler checks that `NeedsUserRepository` is satisfied before the function can run.

## Keeping Traits Focused

A common mistake is defining one massive `AppService` trait with everything in it. Prefer small, focused traits:

```rust
// BAD — one God trait
trait AppService {
    fn get_user(&self, id: u64) -> Effect<User, AppError, ()>;
    fn send_email(&self, to: &str, body: &str) -> Effect<(), AppError, ()>;
    fn charge_card(&self, amount: u64) -> Effect<(), AppError, ()>;
}

// GOOD — separate concerns
trait UserRepository { fn get_user(&self, id: u64) -> Effect<User, DbError, ()>; }
trait EmailService { fn send_email(&self, to: &str, body: &str) -> Effect<(), EmailError, ()>; }
trait PaymentGateway { fn charge(&self, amount: u64) -> Effect<(), PaymentError, ()>; }
```

Small traits are composable. Functions declare exactly which services they need — `NeedsUserRepository + NeedsEmailService` — and the compiler enforces it.