rate_guard/
precision.rs

1//! Precision trait and predefined precision types for Duration-to-tick conversions.
2//!
3//! This module provides the foundation for zero-cost abstractions over different
4//! time precision scales. The Precision trait enables compile-time optimization
5//! of Duration conversions while maintaining type safety and flexibility.
6//!
7//! # Design Philosophy
8//!
9//! The precision system allows rate limiters to work with standard Duration
10//! while internally using optimized tick representations. Different precision
11//! types enable different use cases:
12//!
13//! - `Nanos`: Maximum precision, suitable for high-frequency operations
14//! - `Micros`: Good balance for most applications
15//! - `Millis`: Lower precision, suitable for coarse-grained rate limiting
16//! - `Secs`: Minimal precision for very long-term rate limiting
17//!
18//! # Performance
19//!
20//! All precision conversions use branchless operations that compile to efficient
21//! conditional move (CMOV) instructions, avoiding branch prediction penalties.
22//!
23//! # Examples
24//!
25//! ```rust
26//! use std::time::Duration;
27//! use rate_guard::precision::{Precision, Millis, Nanos};
28//! use rate_guard::types::Uint;
29//! // Convert Duration to ticks using different precisions
30//! let duration = Duration::from_millis(100);
31//!
32//! let millis_ticks = Millis::to_ticks(duration);   // 100 ticks
33//! let nanos_ticks = Nanos::to_ticks(duration);     // 100_000_000 ticks
34//!
35//! // Convert back to Duration
36//! let recovered = Millis::from_ticks(millis_ticks);
37//! assert_eq!(recovered, duration);
38//!
39//! // Generic usage (as used in rate limiters)
40//! fn convert_duration<P: Precision>(duration: Duration) -> Uint {
41//!     P::to_ticks(duration)
42//! }
43//! ```
44
45use std::time::Duration;
46use rate_guard_core::types::Uint;
47
48/// Trait for defining precision scales for Duration-to-tick conversions.
49///
50/// Precision implementations define how Duration values are converted to
51/// internal tick representations and back. Each precision type represents
52/// a different time scale, enabling optimization for different use cases.
53///
54/// # Implementation Requirements
55///
56/// Implementations must satisfy these invariants:
57/// - Bidirectional conversion consistency: `from_ticks(to_ticks(d)) ≈ d`
58/// - Monotonicity: larger durations produce larger tick values
59/// - Zero preservation: `to_ticks(Duration::ZERO) == 0`
60/// - Overflow handling: graceful saturation on overflow using branchless operations
61///
62/// # Performance
63///
64/// All methods should be marked `#[inline(always)]` to enable compile-time
65/// optimization and ensure zero-cost abstractions. The `to_ticks` implementation
66/// uses branchless min() operations that compile to CMOV instructions for optimal
67/// performance.
68///
69/// # Generic Usage
70///
71/// The trait is designed to be used with generic type parameters in rate
72/// limiters and executors:
73///
74/// ```rust
75/// use std::time::Duration;
76/// use rate_guard::precision::{Precision, Millis};
77/// use rate_guard::types::Uint;
78///
79/// fn process_with_precision<P: Precision>(duration: Duration) -> Uint {
80///     P::to_ticks(duration)  // Static dispatch, zero-cost
81/// }
82///
83/// let ticks = process_with_precision::<Millis>(Duration::from_millis(500));
84/// assert_eq!(ticks, 500);
85/// ```
86pub trait Precision {
87    /// Converts a Duration to ticks in this precision scale.
88    ///
89    /// This method uses branchless operations for optimal performance and
90    /// handles overflow by saturating to the maximum tick value using
91    /// min() operations that compile to efficient CMOV instructions.
92    ///
93    /// # Arguments
94    /// * `duration` - The Duration to convert
95    ///
96    /// # Returns
97    /// The number of ticks representing the duration in this precision
98    ///
99    /// # Performance
100    /// 
101    /// This method is branchless and compiles to conditional move instructions,
102    /// avoiding branch prediction penalties.
103    ///
104    /// # Examples
105    ///
106    /// ```rust
107    /// use std::time::Duration;
108    /// use rate_guard::precision::{Precision, Millis};
109    ///
110    /// let duration = Duration::from_millis(500);
111    /// let ticks = Millis::to_ticks(duration);
112    /// assert_eq!(ticks, 500);
113    /// ```
114    fn to_ticks(duration: Duration) -> Uint;
115
116    /// Converts ticks back to a Duration in this precision scale.
117    ///
118    /// This method should be the inverse of `to_ticks` and handle
119    /// large tick values gracefully.
120    ///
121    /// # Arguments
122    /// * `ticks` - The number of ticks to convert
123    ///
124    /// # Returns
125    /// The Duration represented by the tick count
126    ///
127    /// # Examples
128    ///
129    /// ```rust
130    /// use std::time::Duration;
131    /// use rate_guard::precision::{Precision, Millis};
132    ///
133    /// let duration = Millis::from_ticks(1500);
134    /// assert_eq!(duration, Duration::from_millis(1500));
135    /// ```
136    fn from_ticks(ticks: Uint) -> Duration;
137}
138
139/// Nanosecond precision - maximum precision scale.
140///
141/// Uses 1 tick = 1 nanosecond. This provides the highest precision
142/// but may be overkill for many applications. Suitable for:
143/// - High-frequency trading systems
144/// - Precise timing measurements
145/// - Maximum compatibility with system time
146///
147/// # Overflow Behavior
148///
149/// Can represent durations up to ~584 years with u64 ticks.
150/// Uses branchless saturation on overflow for optimal performance.
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub struct Nanos;
153
154impl Precision for Nanos {
155    #[inline(always)]
156    fn to_ticks(duration: Duration) -> Uint {
157        // Branchless overflow handling using min() - compiles to CMOV instruction
158        // This eliminates branch prediction penalties compared to if-else
159        duration.as_nanos().min(Uint::MAX as u128) as Uint
160    }
161
162    #[inline(always)]
163    fn from_ticks(ticks: Uint) -> Duration {
164        Duration::from_nanos(ticks as u64)
165    }
166}
167
168/// Microsecond precision - high precision scale.
169///
170/// Uses 1 tick = 1 microsecond. Provides good precision while
171/// reducing the range of tick values. Suitable for:
172/// - Network latency measurements
173/// - General-purpose rate limiting
174/// - Applications requiring sub-millisecond precision
175///
176/// # Overflow Behavior
177///
178/// Can represent durations up to ~584,000 years with u64 ticks.
179/// Uses branchless saturation on overflow for optimal performance.
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub struct Micros;
182
183impl Precision for Micros {
184    #[inline(always)]
185    fn to_ticks(duration: Duration) -> Uint {
186        // Branchless overflow handling using min() - compiles to CMOV instruction
187        duration.as_micros().min(Uint::MAX as u128) as Uint
188    }
189
190    #[inline(always)]
191    fn from_ticks(ticks: Uint) -> Duration {
192        Duration::from_micros(ticks as u64)
193    }
194}
195
196/// Millisecond precision - standard precision scale.
197///
198/// Uses 1 tick = 1 millisecond. Provides good balance between
199/// precision and efficiency. Suitable for:
200/// - Web API rate limiting
201/// - User-facing applications
202/// - Most general-purpose scenarios
203///
204/// # Overflow Behavior
205///
206/// Can represent durations up to ~584 million years with u64 ticks.
207/// Uses consistent branchless overflow handling for uniformity with other precisions.
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209pub struct Millis;
210
211impl Precision for Millis {
212    #[inline(always)]
213    fn to_ticks(duration: Duration) -> Uint {
214        // Added consistent overflow protection for uniformity, even though
215        // u64::MAX milliseconds (~584 million years) is practically unreachable
216        // Uses branchless min() for consistency with other precision types
217        duration.as_millis().min(Uint::MAX as u128) as Uint
218    }
219
220    #[inline(always)]
221    fn from_ticks(ticks: Uint) -> Duration {
222        Duration::from_millis(ticks as u64)
223    }
224}
225
226/// Second precision - coarse precision scale.
227///
228/// Uses 1 tick = 1 second. Minimal precision but maximum efficiency
229/// and range. Suitable for:
230/// - Long-term rate limiting (hours, days)
231/// - Coarse-grained applications
232/// - Memory-constrained environments
233///
234/// # Overflow Behavior
235///
236/// Can represent durations far beyond practical limits with u64 ticks.
237/// Uses consistent branchless overflow handling for uniformity.
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239pub struct Secs;
240
241impl Precision for Secs {
242    #[inline(always)]
243    fn to_ticks(duration: Duration) -> Uint {
244        // Added consistent overflow protection for uniformity, even though
245        // u64::MAX seconds is practically infinite for rate limiting purposes
246        // Uses branchless min() for consistency with other precision types
247        duration.as_secs().min(Uint::MAX as u64) as Uint
248    }
249
250    #[inline(always)]
251    fn from_ticks(ticks: Uint) -> Duration {
252        Duration::from_secs(ticks as u64)
253    }
254}