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

OptionValue in this exampleWhat it does
window_size_seconds60The limiter looks at the last 60 seconds of activity.
rate_group_size_ms10Requests within 10ms of each other are merged into the same bucket.
hard_limit_factor1.5The suppressed strategy allows 50% burst headroom before full suppression.
suppression_factor_cache_ms100 (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 Allowed or Rejected.
  • The suppressed strategy returns Allowed or Suppressed { is_allowed, suppression_factor }. Always check is_allowed to 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 the rate_limit argument.

Next steps