Concepts
Errors
One DistkitError enum, and a no-panic policy.
Library code in distkit never panics. Anything that can go wrong comes back through a single error type, DistkitError, so ? works uniformly across counters, locks, and rate limiting.
DistkitError
pub enum DistkitError {
InvalidRedisKey(String), // a key failed validation
CounterError(CounterError), // counter operation failed (feature = "counter")
LockError(LockError), // lock operation failed (feature = "lock")
RedisError(redis::RedisError), // transport or Lua script error
MutexPoisoned(&'static str), // an internal sync primitive was poisoned
CustomError(String), // catch-all
TrypemaError(TrypemaError), // rate limiting failed (feature = "trypema")
}
The feature-gated variants only exist when the matching feature is enabled, so you never match on a primitive you didn't compile in.
From is implemented for the inner error types, which is why ? just works:
let key = DistkitRedisKey::try_from("user_123".to_string())?; // InvalidRedisKey on failure
counter.inc(&key, 1).await?; // RedisError / CounterError on failure
Lock errors
Lock-specific failures are modeled separately as LockError, surfaced through DistkitError::LockError:
pub enum LockError {
AcquireFail, // a non-blocking acquire would have to wait
Timeout { waited: Duration }, // a bounded acquire ran out of time
NotOwner, // tried to release a lock you don't hold
InvalidTtl(i64), // ttl must be positive
InvalidOwner, // owner id must not be empty
}
AcquireFail and Timeout are the two you handle routinely - they mean "the lock is busy", not "something broke". See Guard state & errors for how this interacts with lock guards.

