From d898d430cb7534bc80689eeadd25fd3b0e4fe2cb Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Tue, 21 Apr 2026 10:54:30 -0700 Subject: [PATCH 1/2] fix: expose Builder::with_clock and add Default for StdClock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builder::with_clock was pub(crate), leaving no_std users without a way to build a Ratelimiter with custom max_tokens or initial_available — the only public no_std constructor was Ratelimiter::with_clock, which takes neither. Make it pub and document it. Also add impl Default for StdClock to satisfy clippy::new_without_default and pair the ergonomic new() constructor with a Default impl. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 86edfeb..7b51ff0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,13 @@ impl StdClock { } } +#[cfg(feature = "std")] +impl Default for StdClock { + fn default() -> Self { + Self::new() + } +} + #[cfg(feature = "std")] impl Clock for StdClock { fn elapsed(&self) -> Duration { @@ -468,8 +475,12 @@ pub struct Builder { } impl Builder { - #[cfg_attr(not(any(feature = "std", test)), allow(dead_code))] - pub(crate) fn with_clock(rate: u64, clock: C) -> Self { + /// Create a builder configured with the given rate and clock. + /// + /// This is the builder constructor for `no_std` environments or for any + /// caller that wants to supply a custom [`Clock`]. For the standard + /// library clock, use [`Ratelimiter::builder`]. + pub fn with_clock(rate: u64, clock: C) -> Self { Self { rate, max_tokens: None, From 0e829a3904b625e3d1a55c4327e2397d7cd05da4 Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Tue, 21 Apr 2026 10:55:39 -0700 Subject: [PATCH 2/2] feat: default Clock type parameter to StdClock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the Clock generic default to StdClock when the std feature is enabled, so callers can name Ratelimiter and Builder without generics in type position — e.g. fn foo(rl: &Ratelimiter) stays valid. Because StdClock itself is gated by #[cfg(feature = "std")], the default can't be expressed unconditionally; the struct declarations for Ratelimiter and Builder are cfg-duplicated with and without the default. The impl blocks are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 7b51ff0..0f73f71 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,7 +126,29 @@ pub enum TryWaitError { /// /// Tokens accumulate continuously based on elapsed time. Each `try_wait()` /// call consumes one token. A rate of 0 means unlimited (no rate limiting). +/// +/// The `C` type parameter defaults to [`StdClock`] when the `std` feature +/// is enabled, so `Ratelimiter` without generics resolves to the standard +/// configuration. In `no_std` builds the clock must be specified. +#[must_use] +#[cfg(feature = "std")] +pub struct Ratelimiter { + /// Target rate in tokens per second. 0 = unlimited. + rate: AtomicU64, + /// Maximum tokens (burst capacity) in real tokens. + max_tokens: AtomicU64, + /// Available tokens, scaled by TOKEN_SCALE for sub-token precision. + tokens: AtomicU64, + /// Tokens dropped due to bucket overflow, scaled by TOKEN_SCALE. + dropped: AtomicU64, + /// Last refill timestamp in nanoseconds since clock creation. + last_refill_ns: AtomicU64, + /// Clock for measuring elapsed time. + clock: C, +} + #[must_use] +#[cfg(not(feature = "std"))] pub struct Ratelimiter { /// Target rate in tokens per second. 0 = unlimited. rate: AtomicU64, @@ -465,8 +487,22 @@ where } /// Builder for constructing a `Ratelimiter` with custom settings. +/// +/// The `C` type parameter defaults to [`StdClock`] when the `std` feature +/// is enabled, matching [`Ratelimiter`]'s default. +#[derive(Debug, Clone, Copy)] +#[must_use = "call .build() to construct the Ratelimiter"] +#[cfg(feature = "std")] +pub struct Builder { + rate: u64, + max_tokens: Option, + initial_available: u64, + clock: C, +} + #[derive(Debug, Clone, Copy)] #[must_use = "call .build() to construct the Ratelimiter"] +#[cfg(not(feature = "std"))] pub struct Builder { rate: u64, max_tokens: Option, @@ -831,6 +867,19 @@ mod tests { assert_eq!(rl.rate(), 1000); } + // Proves the `C: Clock = StdClock` default works in type position — + // callers can name `Ratelimiter` / `Builder` without generics. + #[cfg(feature = "std")] + #[test] + fn type_default_clock() { + let rl: Ratelimiter = Ratelimiter::new(1000); + assert_eq!(rl.rate(), 1000); + + let b: Builder = Ratelimiter::builder(1000); + let rl = b.max_tokens(10).build().unwrap(); + assert_eq!(rl.max_tokens(), 10); + } + #[cfg(feature = "std")] #[test] fn multithread() {