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}