A flexible and type-safe rate limiting library for Rust.
Rate-Guard provides a unified interface for multiple rate limiting algorithms with Duration-based configuration, type-safe time handling, and comprehensive error reporting. It's designed to integrate seamlessly with Rust's async ecosystem while maintaining high performance through zero-cost abstractions.
- Duration-Based Configuration: All timing parameters use
std::time::Duration
- Multiple Algorithms: token bucket, fixed window counter, sliding window counter, and approximate sliding window
- Time Source Abstraction: Pluggable time sources (mock, std::time, etc.)
- Precision Control: Compile-time precision selection for optimal performance
- Builder Pattern: Fluent configuration API with compile-time validation
- Zero-Cost Abstractions: High-level ergonomics without runtime overhead
- Comprehensive Testing: Built-in mock time source for deterministic testing
Add this to your Cargo.toml
:
[dependencies]
rate-guard = "0.1.0"
# Optional: Enable std::time support
rate-guard = { version = "0.1.0", features = ["std-time"] }
Basic usage:
use rate_guard::{Nanos, MockTimeSource, RateLimit};
use rate_guard::limits::TokenBucketBuilder;
use std::time::Duration;
// Create a token bucket that allows 100 requests with 10 refills every 100ms
let bucket = TokenBucketBuilder::builder()
.capacity(100)
.refill_amount(10)
.refill_every(Duration::from_millis(100))
.with_time(MockTimeSource::new())
.with_precision::<Nanos>()
.build()
.unwrap();
// Check if request is allowed
match bucket.try_acquire(1) {
Ok(()) => println!("Request allowed"),
Err(_) => println!("Rate limited"),
}
// Check remaining capacity
println!("Remaining: {}", bucket.capacity_remaining().unwrap());
Allows bursts up to capacity with sustained rate limiting:
use rate_guard::{Millis, MockTimeSource, RateLimit};
use rate_guard::limits::TokenBucketBuilder;
use std::time::Duration;
let time_source = MockTimeSource::new();
let bucket = TokenBucketBuilder::builder()
.capacity(100)
.refill_amount(10)
.refill_every(Duration::from_millis(100))
.with_time(time_source.clone())
.with_precision::<Millis>()
.build()
.unwrap();
assert!(bucket.try_acquire(50).is_ok());
time_source.advance(Duration::from_millis(200));
assert_eq!(bucket.capacity_remaining().unwrap(), 70); // 50 + 20 refill
use rate_guard::{Millis, MockTimeSource, RateLimit};
use rate_guard::limits::FixedWindowCounterBuilder;
use std::time::Duration;
let counter = FixedWindowCounterBuilder::builder()
.capacity(100)
.window_duration(Duration::from_secs(60))
.with_time(MockTimeSource::new())
.with_precision::<Millis>()
.build()
.unwrap();
assert!(counter.try_acquire(50).is_ok());
use rate_guard::{Millis, MockTimeSource, RateLimit};
use rate_guard::limits::SlidingWindowCounterBuilder;
use std::time::Duration;
let counter = SlidingWindowCounterBuilder::builder()
.capacity(100)
.bucket_duration(Duration::from_secs(10))
.bucket_count(6)
.with_time(MockTimeSource::new())
.with_precision::<Millis>()
.build()
.unwrap();
assert!(counter.try_acquire(25).is_ok());
use rate_guard::{Millis, MockTimeSource, RateLimit};
use rate_guard::limits::ApproximateSlidingWindowBuilder;
use std::time::Duration;
let window = ApproximateSlidingWindowBuilder::builder()
.capacity(100)
.window_duration(Duration::from_secs(60))
.with_time(MockTimeSource::new())
.with_precision::<Millis>()
.build()
.unwrap();
assert!(window.try_acquire(30).is_ok());
For detailed production environment integration examples, please refer to the examples/
directory:
token_bucket_sync.rs
- Token Bucket sync exampletoken_bucket_async.rs
- Token Bucket async examplefixed_window_counter_sync.rs
- Fixed window counter sync examplefixed_window_counter_async.rs
- Fixed window counter async examplesliding_window_counter_sync.rs
- Sliding window counter sync examplesliding_window_counter_async.rs
- Sliding window counter async exampleapprox_sliding_window_sync.rs
- Approximate sliding window sync exampleapprox_sliding_window_async.rs
- Approximate sliding window async example
#[cfg(feature = "std-time")]
use rate_guard::{Nanos, StdTimeSource, RateLimit};
#[cfg(feature = "std-time")]
use rate_guard::limits::TokenBucketBuilder;
#[cfg(feature = "std-time")]
use std::time::Duration;
#[cfg(feature = "std-time")]
fn create_production_limiter() -> impl RateLimit {
TokenBucketBuilder::builder()
.capacity(1000)
.refill_amount(100)
.refill_every(Duration::from_millis(100))
.with_time(StdTimeSource::new())
.with_precision::<Nanos>()
.build()
.unwrap()
}
Tip: Start with conservative settings and adjust based on your actual requirements
use rate_guard::{Millis, MockTimeSource, RateLimit, RateLimitError};
use rate_guard::limits::TokenBucketBuilder;
use std::time::Duration;
let bucket = TokenBucketBuilder::builder()
.capacity(10)
.refill_amount(1)
.refill_every(Duration::from_secs(1))
.with_time(MockTimeSource::new())
.with_precision::<Millis>()
.build()
.unwrap();
bucket.try_acquire(10).unwrap();
match bucket.try_acquire_verbose(5) {
Ok(()) => println!("Request allowed"),
Err(RateLimitError::InsufficientCapacity { acquiring, available, retry_after }) => {
println!("Need {} tokens, only {} available, retry after {:?}",
acquiring, available, retry_after);
}
Err(e) => println!("Other error: {}", e),
}
use rate_guard::{Millis, MockTimeSource, RateLimit};
use rate_guard::limits::TokenBucketBuilder;
use std::time::Duration;
let time_source = MockTimeSource::new();
let bucket = TokenBucketBuilder::builder()
.capacity(50)
.refill_amount(5)
.refill_every(Duration::from_millis(200))
.with_time(time_source.clone())
.with_precision::<Millis>()
.build()
.unwrap();
assert!(bucket.try_acquire(50).is_ok());
assert_eq!(bucket.capacity_remaining().unwrap(), 0);
time_source.advance(Duration::from_millis(200));
assert_eq!(bucket.capacity_remaining().unwrap(), 5);
use rate_guard::{Nanos, Micros, Millis, Secs, MockTimeSource};
use rate_guard::limits::TokenBucketBuilder;
use std::time::Duration;
let nano_bucket = TokenBucketBuilder::builder()
.capacity(100).refill_amount(10)
.refill_every(Duration::from_nanos(100))
.with_time(MockTimeSource::new())
.with_precision::<Nanos>()
.build().unwrap();
let micro_bucket = TokenBucketBuilder::builder()
.capacity(100).refill_amount(10)
.refill_every(Duration::from_micros(100))
.with_time(MockTimeSource::new())
.with_precision::<Micros>()
.build().unwrap();
let milli_bucket = TokenBucketBuilder::builder()
.capacity(100).refill_amount(10)
.refill_every(Duration::from_millis(100))
.with_time(MockTimeSource::new())
.with_precision::<Millis>()
.build().unwrap();
let sec_bucket = TokenBucketBuilder::builder()
.capacity(100).refill_amount(10)
.refill_every(Duration::from_secs(1))
.with_time(MockTimeSource::new())
.with_precision::<Secs>()
.build().unwrap();
std-time
- Standard library time source (recommended for production)tokio-time
- Tokio time source (for async environments)
tick-u64
(default) - 64-bit precision, suitable for most scenariostick-u128
- 128-bit precision, for extreme long-duration use cases
# Default configuration
rate-guard = "0.1.0"
# Production recommended
rate-guard = { version = "0.1.0", features = ["std-time"] }
# Special requirements: high precision
rate-guard = { version = "0.1.0", features = ["std-time", "tick-u128"] }
This crate supports Rust 1.70.0 or newer.
- Basic functionality: Works with the specified MSRV
tokio-time
feature: Requires Rust 1.70.0+ (tokio requirement)
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Contributions are welcome! Please feel free to submit a Pull Request.