Skip to main content

moirai/
revision.rs

1//! Checked monotonic revisions and fixed-width revision keys for cache identity.
2//!
3//! [`Revision`] backs query and storage invalidation counters. [`RevisionKey`] groups ordered
4//! revision fields into deterministic comparison keys.
5
6use core::fmt;
7
8/// A checked monotonic revision counter.
9#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
10pub struct Revision(u64);
11
12impl Revision {
13    /// The initial revision.
14    pub const ZERO: Self = Self(0);
15
16    /// Returns the raw revision value.
17    pub const fn get(self) -> u64 {
18        self.0
19    }
20
21    /// Advances this revision, leaving it unchanged if the counter is exhausted.
22    pub fn advance(&mut self) -> Result<(), RevisionExhausted> {
23        let next = self.0.checked_add(1).ok_or(RevisionExhausted)?;
24        self.0 = next;
25        Ok(())
26    }
27}
28
29/// Returned when a [`Revision`] cannot advance without overflowing.
30#[derive(Copy, Clone, Debug, Eq, PartialEq)]
31pub struct RevisionExhausted;
32
33impl fmt::Display for RevisionExhausted {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        f.write_str("revision exhausted")
36    }
37}
38
39#[cfg(feature = "std")]
40impl std::error::Error for RevisionExhausted {}
41
42/// A fixed-width collection of revisions suitable for cache keys.
43#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
44pub struct RevisionKey<const N: usize>([Revision; N]);
45
46impl<const N: usize> RevisionKey<N> {
47    /// Creates a key from its ordered revision fields.
48    pub const fn new(revisions: [Revision; N]) -> Self {
49        Self(revisions)
50    }
51
52    /// Returns the ordered revision fields.
53    pub const fn as_array(&self) -> &[Revision; N] {
54        &self.0
55    }
56}
57
58impl<const N: usize> From<[Revision; N]> for RevisionKey<N> {
59    fn from(revisions: [Revision; N]) -> Self {
60        Self::new(revisions)
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use alloc::string::ToString;
68
69    #[test]
70    fn revision_advances_from_zero() {
71        let mut revision = Revision::ZERO;
72        revision.advance().expect("advance");
73        assert_eq!(revision.get(), 1);
74    }
75
76    #[test]
77    fn exhausted_revision_is_unchanged() {
78        let mut revision = Revision(u64::MAX);
79        assert_eq!(revision.advance(), Err(RevisionExhausted));
80        assert_eq!(revision.get(), u64::MAX);
81        assert_eq!(RevisionExhausted.to_string(), "revision exhausted");
82    }
83
84    #[test]
85    fn key_round_trips_its_array() {
86        let key = RevisionKey::from([Revision::ZERO; 3]);
87        assert_eq!(key.as_array(), &[Revision::ZERO; 3]);
88    }
89}