Getting Started
Quickstart (Local)
Get started with in-process rate limiting using the local provider — no Redis required.
The local provider stores all state in-process. It requires no external dependencies and has sub-microsecond latency. This is the simplest way to get started with Trypema.
Step 1: Add the dependency
[dependencies]
trypema = "0.1"
Step 2: Create the rate limiter
Every Trypema program starts by creating a RateLimiter with configuration options. Wrap it in Arc so it can be shared across threads:
use std::sync::Arc;
use trypema::{
HardLimitFactor, RateGroupSizeMs, RateLimit, RateLimitDecision,
RateLimiter, RateLimiterOptions, SuppressionFactorCacheMs, WindowSizeSeconds,
};
use trypema::local::LocalRateLimiterOptions;
fn main() {
// Create the rate limiter with a 60-second sliding window.
let rl = Arc::new(RateLimiter::new(RateLimiterOptions {
local: LocalRateLimiterOptions {
window_size_seconds: WindowSizeSeconds::try_from(60).unwrap(),
rate_group_size_ms: RateGroupSizeMs::try_from(10).unwrap(),
hard_limit_factor: HardLimitFactor::try_from(1.5).unwrap(),
suppression_factor_cache_ms: SuppressionFactorCacheMs::default(),
},
}));
// Start background cleanup to remove stale keys (recommended).
// This is idempotent — calling it multiple times is a no-op.
rl.run_cleanup_loop();
// Define a rate limit of 5 requests per second.
// With a 60-second window, the window capacity is 60 * 5 = 300 requests.
let rate_limit = RateLimit::try_from(5.0).unwrap();
// --- Use the absolute strategy ---
let decision = rl.local().absolute().inc("user_123", &rate_limit, 1);
handle_absolute_decision(decision);
// --- Use the suppressed strategy ---
let decision = rl.local().suppressed().inc("user_123", &rate_limit, 1);
handle_suppressed_decision(decision);
}
fn handle_absolute_decision(decision: RateLimitDecision) {
match decision {
RateLimitDecision::Allowed => {
println!("Request allowed — proceed.");
}
RateLimitDecision::Rejected {
retry_after_ms,
remaining_after_waiting,
..
} => {
// The request was rejected because the key is over its limit.
// retry_after_ms is a best-effort hint for when capacity opens up.
println!(
"Rejected. Retry in ~{}ms ({} requests will remain in window).",
retry_after_ms, remaining_after_waiting
);
}
RateLimitDecision::Suppressed { .. } => {
// The absolute strategy never returns Suppressed.
unreachable!();
}
}
}
fn handle_suppressed_decision(decision: RateLimitDecision) {
match decision {
RateLimitDecision::Allowed => {
// Below capacity — no suppression active.
println!("Allowed (below capacity).");
}
RateLimitDecision::Suppressed {
is_allowed: true,
suppression_factor,
} => {
// Suppression is active, but this request was probabilistically allowed.
println!(
"Allowed (suppression active at {:.0}%).",
suppression_factor * 100.0
);
}
RateLimitDecision::Suppressed {
is_allowed: false,
suppression_factor,
} => {
// This request was probabilistically denied. Do NOT proceed.
println!(
"Denied (suppression at {:.0}%).",
suppression_factor * 100.0
);
}
RateLimitDecision::Rejected { .. } => {
// The suppressed strategy never returns Rejected.
unreachable!();
}
}
}
What each option means
| Option | Value in this example | What it does |
|---|---|---|
window_size_seconds | 60 | The limiter looks at the last 60 seconds of activity. |
rate_group_size_ms | 10 | Requests within 10ms of each other are merged into the same bucket. |
hard_limit_factor | 1.5 | The suppressed strategy allows 50% burst headroom before full suppression. |
suppression_factor_cache_ms | 100 (default) | The suppression factor is cached for 100ms before recomputing. |
For a full explanation of every option, see Configuration & Tuning.
Key points
rl.local()gives you the local provider..absolute()gives you the absolute (strict) strategy..suppressed()gives you the suppressed (probabilistic) strategy.inc(key, &rate_limit, count)checks admission and records the increment in one call.- The absolute strategy returns
AllowedorRejected. - The suppressed strategy returns
AllowedorSuppressed { is_allowed, suppression_factor }. Always checkis_allowedto decide whether to proceed. - Rate limits are sticky: the first
inc()call for a key stores the limit. Subsequent calls for the same key ignore therate_limitargument.
Next steps
- Quickstart (Redis) -- distribute rate limiting across processes
- Quickstart (Hybrid) -- high-throughput with Redis sync
- Keys -- understand how keys work
- Decisions -- understand
Allowed,Rejected, andSuppressed

