Skip to main content

typestates/
typestates.rs

1//! Typestate proofs in use.
2//!
3//! Each typestate is a zero-cost newtype: you construct the proof once at a
4//! boundary (a parse, a config value, a constant), then *spend* it with an op
5//! that exploits the invariant to drop a branch, an `Option`, or a division.
6//! Everything below is straight-line after the proof is built.
7//!
8//! Run with: `cargo run --example typestates`
9//! (the constant-time section also needs `--features ct`).
10
11use std::collections::BTreeMap;
12
13use const_num_traits::{
14    BitIndex, BitIndexOps, DivNonZero, Finite, HasNonZero, NonMin, NonNegative, Odd, Positive,
15    PowerOfTwo, PowerOfTwoOps,
16};
17
18/// `PowerOfTwo`: reduce / divide / align by a `2^k` modulus as masks and
19/// shifts, with no division and no divide-by-zero branch.
20fn power_of_two() {
21    let radix = 256u64;
22    let p = PowerOfTwo::<u64>::new(radix).expect("256 is 2^8");
23
24    // `x % radix` and `x / radix` without a division instruction
25    assert_eq!(700u64.rem_pow2(p), 700 % 256); // & 0xFF
26    assert_eq!(700u64.div_pow2(p), 700 / 256); // >> 8
27
28    // align-up to the next multiple, branch-free; checked form for the edge
29    assert_eq!(700u64.next_multiple_of_pow2(p), 768);
30    assert_eq!(u64::MAX.checked_next_multiple_of_pow2(p), None);
31
32    // built once from a runtime value, spent in a loop with no per-iter recheck
33    let mut folded = 0u64;
34    for limb in [10u64, 600, 70_000] {
35        folded ^= limb.rem_pow2(p);
36    }
37    assert_eq!(folded, (10 ^ 600 ^ 70_000) & 0xFF);
38}
39
40/// `BitIndex`: shift by an amount proven `< BITS`, so the shift carries no
41/// "is the amount too large?" overflow-check branch.
42fn bit_index() {
43    let pos = BitIndex::<u64>::new(40).expect("40 < 64");
44    assert_eq!((1u64).shl_index(pos), 1u64 << 40);
45    assert_eq!((1u64 << 63).shr_index(pos), (1u64 << 63) >> 40);
46
47    // out-of-range index is rejected at construction, not at the shift
48    assert!(BitIndex::<u64>::new(64).is_none());
49}
50
51/// `HasNonZero` + `DivNonZero`: division by a value proven non-zero — no
52/// `Option` result, no divide-by-zero path.
53fn nonzero_division() {
54    let d = 17u32.into_nonzero().expect("17 != 0");
55    assert_eq!(100u32.div_nonzero(d), 100 / 17);
56    assert_eq!(100u32.rem_nonzero(d), 100 % 17);
57    assert!(0u32.into_nonzero().is_none());
58}
59
60/// `NonNegative` / `Positive`: a value proven `>= 0` casts to unsigned with no
61/// `TryFrom`/`Option`, and takes `isqrt` without the negative-input panic path.
62fn signed_range() {
63    let nn = NonNegative::<i32>::new(81).expect("81 >= 0");
64    let u: u32 = nn.to_unsigned(); // infallible, bit-preserving
65    assert_eq!(u, 81);
66    assert_eq!(nn.isqrt(), 9); // total: input is known non-negative
67
68    // a positive value narrows straight into a NonZero divisor
69    let nz = Positive::<i32>::new(7).expect("7 > 0").into_nonzero();
70    assert_eq!(nz.get(), 7);
71}
72
73/// `NonMin`: a signed value proven `!= MIN` has total `abs`/`neg` and total
74/// signed division — the `MIN.abs()` and `MIN / -1` overflow cases are gone.
75fn nonmin() {
76    let a = NonMin::<i32>::new(-7).expect("-7 != MIN");
77    assert_eq!(a.abs(), 7); // no overflow branch
78    assert_eq!(a.neg(), 7);
79
80    // signed division by NonZero: MIN/-1 is impossible by construction
81    let d = core::num::NonZero::new(2).unwrap();
82    assert_eq!(a.div_nonzero(d), -7 / 2);
83
84    // Positive/NonNegative narrow into NonMin for free
85    let q = Positive::<i32>::new(9).unwrap().into_nonmin();
86    assert_eq!(q.abs(), 9);
87
88    assert!(NonMin::<i32>::new(i32::MIN).is_none());
89}
90
91/// `Odd`: the precondition lives in the type. The proof is const-constructible
92/// (on nightly with `--features nightly`), so a constant modulus is checked at
93/// compile time rather than via a runtime `.unwrap()`.
94fn odd_modulus() {
95    // a parsed/odd modulus, proven once
96    let m = Odd::<u64>::new(0xFFFF_FFFF_FFFF_FFC5).expect("a large odd prime");
97    // `Odd ⇒ non-zero` (zero is even), so this one proof covers both the
98    // "odd" and "non-zero" preconditions at once.
99    assert_eq!(m.get() & 1, 1);
100
101    assert!(Odd::<u64>::new(0).is_none()); // zero is even
102    assert!(Odd::<u64>::new(10).is_none());
103}
104
105/// `Finite`: finite floats gain a total order, so they sort and key a
106/// `BTreeMap` — neither of which bare `f32`/`f64` can do (NaN breaks `Ord`).
107fn finite_floats() {
108    let raw = [2.0f64, f64::NAN, 1.0, f64::INFINITY, 0.5];
109
110    // keep only the finite values; each carries its proof
111    let mut xs: Vec<Finite<f64>> = raw.iter().copied().filter_map(Finite::<f64>::new).collect();
112    xs.sort(); // total Ord — no `partial_cmp().unwrap()`
113    let sorted: Vec<f64> = xs.iter().map(|f| f.get()).collect();
114    assert_eq!(sorted, [0.5, 1.0, 2.0]);
115
116    // finite floats as map keys
117    let mut table: BTreeMap<Finite<f64>, &str> = BTreeMap::new();
118    table.insert(Finite::<f64>::new(1.0).unwrap(), "one");
119    table.insert(Finite::<f64>::new(2.0).unwrap(), "two");
120    assert_eq!(table.get(&Finite::<f64>::new(1.0).unwrap()), Some(&"one"));
121}
122
123/// Constant-time constructors (`--features ct`): the same proofs, but the
124/// *check* is masked into a `subtle::CtOption` instead of branching — for when
125/// the value is secret.
126#[cfg(feature = "ct")]
127fn constant_time() {
128    use const_num_traits::CtNonZero; // brings `into_nonzero_ct` into scope
129
130    // no `bool` is produced from the secret; the option is masked
131    assert!(bool::from(Odd::<u64>::new_ct(0xABCD_1235).is_some()));
132    assert!(bool::from(Odd::<u64>::new_ct(0xABCD_1234).is_none()));
133    assert!(bool::from(42u32.into_nonzero_ct().is_some()));
134    assert!(bool::from(0u32.into_nonzero_ct().is_none()));
135}
136
137fn main() {
138    power_of_two();
139    bit_index();
140    nonzero_division();
141    signed_range();
142    nonmin();
143    odd_modulus();
144    finite_floats();
145    #[cfg(feature = "ct")]
146    constant_time();
147
148    println!("all typestate examples ran");
149}