Pools — Reusing Expensive Resources
Pool<A, E> and KeyedPool<K, A, E> manage reusable values with a capacity gate. A checkout is tied to a Scope: when the scope closes, the value is returned to the idle list unless invalidated.
Pool
use effectful::Pool;
let pool_effect = Pool::make(10, || open_connection("postgres://localhost/app"));
let pool: Pool<Connection, DbError> = run_blocking(pool_effect, ())?;
Pool::make(capacity, factory) returns Effect<Pool<A, E>, Never, ()>. The factory is an effect that creates a fresh value when no reusable idle value is available.
Checking Out
use effectful::{Scope, run_blocking};
let scope = Scope::make();
let conn = run_blocking(pool.get(), scope.clone())?;
// Use conn while scope is open.
scope.close(); // returns conn to the pool's idle list
pool.get() returns Effect<A, E, Scope>. It acquires capacity, reuses an idle value or runs the factory, and registers a finalizer in the provided scope.
Invalidating Values
run_blocking(pool.invalidate(conn.clone()), ())?;
Invalidated values are not reused when their checkout scope closes.
KeyedPool
Use KeyedPool when resource creation depends on a key.
use effectful::KeyedPool;
let pools_effect = KeyedPool::make(20, |key: DbRole| open_connection_for(key));
let pools: KeyedPool<DbRole, Connection, DbError> = run_blocking(pools_effect, ())?;
let scope = Scope::make();
let primary = run_blocking(pools.get(DbRole::Primary), scope.clone())?;
let replica = run_blocking(pools.get(DbRole::Replica), scope.clone())?;
scope.close();
Capacity is global across all keys. Idle values are stored per key.
TTL
Both pool types have make_with_ttl. Idle values older than the TTL are discarded on checkout.
let pool = run_blocking(
Pool::make_with_ttl(10, Duration::from_secs(300), || open_connection(url.clone())),
(),
)?;
Pool as a Service
In applications, build the pool at startup and provide it as a service or context value. Business code should depend on the pool abstraction and checkout inside an explicit Scope.