seed = "203"limit = "500"// A small value object with an immutable-reference method, so the tour has a// `&T` beside its `&mut T` — the two are different forms and only one of them// is affected by the recorder gap NR-04.pub struct Bounds { pub low: u32, pub high: u32,}impl Bounds { pub fn new(low: u32, high: u32) -> Self { Bounds { low, high } } // Takes `self` by IMMUTABLE reference and returns a new value. Reading // through `&self` is recorded correctly; it is only writing through a // reference that is not (NR-04). pub unconstrained fn span(self: &Self) -> u32 { (*self).high - (*self).low } pub unconstrained fn widen(self, by: u32) -> Self { let grown = Bounds { low: self.low, high: self.high + by }; assert(grown.span_by_value() >= self.span_by_value()); grown } pub unconstrained fn span_by_value(self) -> u32 { self.high - self.low }}// TOUR: bounds, limits and overflow//// The subject is what happens at the EDGES of a type. In a circuit this is not// a curiosity: an addition that overflows is a failed constraint, and the// difference between "this wrapped" and "this aborted" is the difference// between a proof and no proof. A debugger is where that becomes visible.//// This program exists because `tools/noir-coverage.mjs` enumerates the// compiler's own AST and reported a cluster of forms as neither demonstrated// nor explained — globals, type aliases, shadowing, `loop`, the shift and// compound-assignment operators, `Option`, `u32::max_value()` and the wrapping// arithmetic traits. They are gathered here rather than sprinkled about because// they share a subject: every one of them is about the boundary of a type.mod checked;use checked::Bounds;use std::ops::WrappingAdd;// ── a global constant, and a type alias over it ───────────────────────────// A `global` is evaluated once at compile time and is in scope everywhere; the// alias gives the width a name that says what it is FOR rather than how wide it// is, which is the whole reason aliases exist.global HEADROOM: u32 = 8;type Counter = u32;// ── a NUMERIC type alias: the alias computes a length ─────────────────────// `Window<N>` is `N * 2` used in array-length position. The generic is a value,// not a type, so this is arithmetic the compiler performs on types.type Window<let N: u32>: u32 = N * 2;unconstrained fn saturating_add(a: u32, b: u32) -> u32 { // `u32::max_value()` is a method reached through the TYPE rather than // through a value — a path form of its own. let ceiling = u32::max_value(); if a > ceiling - b { ceiling } else { a + b }}unconstrained fn wrapping_walk(start: u32) -> u32 { // The wrapping traits are the language's way of asking for modular // arithmetic explicitly, instead of getting it by accident. `wrapping_add` // past the ceiling comes back to zero, and the recording shows it doing so // one step at a time. let mut v = start; for _ in 0..3 { v = v.wrapping_add(u32::max_value() / 2); } v}unconstrained fn shift_ladder(seed: u32) -> u32 { // Every compound assignment operator the language has, in one place, plus // both shifts. `seed` is masked first so the ladder cannot overflow — // which is itself the point of the mask. let mut v = seed & 0xff; v <<= 3; v |= 1; v ^= 10; v &= 0xfff; v >>= 2; v += HEADROOM; v -= 1; v *= 2; v /= 3; v %= 1000; v}unconstrained fn first_over(limit: u32) -> Option<u32> { // `loop` — unconstrained only, and it must contain a reachable `break`. // The trip count is in no bound anywhere in the source, so the recording is // the only place it exists. // // `Option` is how a Noir function says "there may be no answer" without a // sentinel value a caller could mistake for a real one. let mut n: Counter = 1; loop { n = n * 3; if n > limit { break; } } if n > limit { Option::some(n) } else { Option::none() }}unconstrained fn main(seed: u32, limit: pub u32) -> pub u32 { let sat = saturating_add(u32::max_value() - 2, 10); let wrapped = wrapping_walk(seed); let laddered = shift_ladder(seed); // SHADOWING: the second `bounds` is a new binding with the same name, and // the first is still in the recording at the steps before this line. That // is the case a values pane has to get right and a printout cannot show at // all. let bounds = Bounds::new(0, HEADROOM); let bounds = bounds.widen(limit); // Destructuring a struct, and a tuple pattern beside it. // `span` takes `&self` — reading THROUGH a reference, which the recorder // handles correctly. Only writing through one does not (NR-04). let by_ref = bounds.span(); let Bounds { low, high } = bounds; let (span, midpoint) = (high - low, (high + low) / 2); // A window whose length is computed by the numeric alias: N = 3, so 6. // The turbofish is required in array-length position. let window: [u32; Window::<3>] = [0, 1, 2, 3, 4, 5]; // `_` discards a value the program does not need, which is a binding form // rather than an absence of one. let _ = window[5]; let found = first_over(limit); let reached = if found.is_some() { found.unwrap() } else { 0 }; assert_eq(sat, u32::max_value()); assert(span >= HEADROOM); assert_eq(by_ref, span); assert(window.len() == 6); sat / 1000 + wrapped % 1000 + laddered + span + midpoint + reached % 1000}[package]name = "tour_limits"type = "bin"authors = ["BlockTracer capability tour"][dependencies]The recorded event stream is in the published recording. Reading it needs the replay engine, which this page has not started.
The call structure is in the published recording. Reading it needs the replay engine, which this page has not started.
The recorded values are in the published recording. Reading them needs the replay engine, which this page has not started.