Capability services

A capability service is a Rust type that names a dependency in Env. The Cap<T> wrapper implements CapabilityKey for any cloneable T, so you use the service type directly in caps!, require!, and #[provides].

Declaring a service

#![allow(unused)]
fn main() {
// Concrete value type
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Counter(pub u32);

// Trait-backed service (typical for ports/adapters)
pub type Database = Arc<dyn DbClient>;
pub type UserRepo = Arc<dyn UserRepository>;
}

Database and Cache can both wrap a Pool but remain distinct capability identities — you cannot pass one where the other is required.

Registering values

At runtime, values live in Env:

#![allow(unused)]
fn main() {
use id_effect::{Cap, Env};

let mut env = Env::new();
env.insert::<Cap<Database>>(main_pool);
env.insert::<Cap<Cache>>(cache_pool);
}

Or let a ProviderSpec insert them during run_with.

Why named services help the compiler

#![allow(unused)]
fn main() {
fn needs_database() -> Effect<A, E, caps!(Database)> { ... }
fn needs_cache() -> Effect<A, E, caps!(Cache)> { ... }
}

Providing the wrong service is a type error at the call site, not a silent runtime swap.

Service traits

Define a focused trait, then a type alias for the handle stored in Env:

#![allow(unused)]
fn main() {
pub trait UserRepository: Send + Sync {
    fn get_user(&self, id: u64) -> Effect<User, DbError, ()>;
}
pub type UserRepo = Arc<dyn UserRepository>;
}

Trait methods keep R = () — the caller carries Database / UserRepo in its caps! list.

Summary

ItemRole
Service type TName used in caps!(T) and require!(T)
Cap<T>Internal CapabilityKey with Value = T
Env::insert::<Cap<T>>(v)Register a service
Needs<T>Bound: environment contains T

Named services eliminate the positional problem. The next section introduces Env — how those services are stored at runtime.