-
Notifications
You must be signed in to change notification settings - Fork 24
Description
Proposal
Problem statement
core::time::Duration is a numeric type, but lacks some of the common arithmetic functions provided by analogous types like unsigned integers uN. In particular, the only available Duration by Duration division functions return floats (div_duration_f64 and div_duration_f32), and Duration lacks some of the newer forms of integer arithmetic functions such as strict_{add,div,mul,sub} and (checked_)div_exact (whatever that family of functions will end up looking like when #![feature(exact_div)] is stabilized).
Motivating examples or use cases
In a codebase targeting embedded systems that run tasks at fixed periods ("control cycles"), I find myself manually implementing operations like Duration by Duration integer division (e.g., "how many control cycles make up a timeout?", "how many cycles should I wait between publishing data at a low rate to avoid overwhelming a consumer?") and performing various checked arithmetic operations and unwrapping them.
/// Some task runs every 10ms
const CYCLE_PERIOD: Duration = Duration::from_millis(10);
/// Number of cycles that make up a 2s timeout
// In practice, this needs to be converted to a smaller type such as u16,
// which will be easier when .try_into().unwrap() works in const
const TIMEOUT_CYCLES: u128 = Duration::from_secs(2).as_nanos().div_ceil(CYCLE_PERIOD.as_nanos());
/// The task publishes data to a slower task that runs every 500ms
const SLOW_TASK_CYCLE_PERIOD: Duration = Duration::from_millis(500);
/// To make sure the slow task receives data every cycle but avoid overwhelming it with high-rate data,
/// publish data much slower than the fast task's cycle but twice as fast as (at half the period of)
/// the slow task's cycle
const SLOW_DATA_DOWNSAMPLE_RATE: u128 =
(SLOW_TASK_CYCLE_PERIOD.checked_div(2).unwrap()) // period at which to publish data
.as_nanos() / CYCLE_PERIOD.as_nanos(); // data should be published every N "fast task" cyclesGitHub search shows several manual implementations of these operations:
- https://github.com/shadow/shadow/blob/0bdea547e94c8901f16c48c4140d2fff91932442/src/main/network/relay/token_bucket.rs#L125-L132
- https://github.com/shadow3aaa/surfaceflinger_hook/blob/5f7ddf005cf769e02598198868998169be1d0286/src/fps.rs#L32
- https://github.com/NEWSLabNTU/multi-stream-synchronizer/blob/75ffd87e1ac691126013a52b06bd249698c919c5/ALGORITHM.md?plain=1#L280-L282
Solution sketch
impl Duration {
// Integer division alternatives to div_duration_{f32,f64}()
// div_duration_floor() could be just div_duration(), but _floor seems clearer
// to disambiguate from _{f32,f64}.
// Would parallel uN::div_floor() if that stabilizes (https://github.com/rust-lang/rust/issues/88581)
const fn div_duration_floor(self, rhs: Self) -> u128
const fn div_duration_ceil(self, rhs: Self) -> u128
// uN::(checked_)div_exact() equivalents, mirroring whatever that API ends up looking like
const fn div_duration_exact(self, rhs: Self) -> Option<u128>
// Strict (explicitly panicking) Duration x u32 arithmetic
const fn strict_mul(self, rhs: u32) -> Self
const fn strict_div(self, rhs: u32) -> Self
// Strict (explicitly panicking) Duration x Duration arithmetic
const fn strict_add(self, rhs: Self) -> Self
const fn strict_sub(self, rhs: Self) -> Self
// Operations I have less immediate need for, but might be worth including for consistency?
// Explicitly panicking and checked variants of div_duration_{floor,ceil}() on divide-by-zero
const fn strict_div_duration_floor(self, rhs: Self) -> u128
const fn strict_div_duration_ceil(self, rhs: Self) -> u128
const fn checked_div_duration_floor(self, rhs: Self) -> Option<u128>
const fn checked_div_duration_ceil(self, rhs: Self) -> Option<u128>
// Parallels to uN::{is,(checked_)next}_multiple_of()
// Seems natural to include these alongside exact division
const fn is_multiple_of(self, rhs: Self) -> bool
const fn next_multiple_of(self, rhs: Self) -> Self
const fn checked_next_multiple_of(self, rhs: Self) -> Option<Self>
}Alternatives
- Don't add these APIs (all can be written using existing APIs), or implement them in a crates.io crate.
- Relatively trivial functions like this aren't worth pulling in a dependency for (or, at least for me, even worth defining a small internal library for).
Durationseems comparable touN, for which the standard library has no shortage of technically redundant helpers for the sake of discoverability/clarity of code. - The ways to express
DurationbyDurationdivision APIs using existing APIs involve going through.as_nanos(). This requires knowing the precision thatDurationprovides for it to be clear to the reader that.as_nanos()is lossless.
- Relatively trivial functions like this aren't worth pulling in a dependency for (or, at least for me, even worth defining a small internal library for).
Links and related work
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
- We think this problem seems worth solving, and the standard library might be the right place to solve it.
- We think that this probably doesn't belong in the standard library.
Second, if there's a concrete solution:
- We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
- We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.