Skip to main content

hydro_lang/
nondet.rs

1//! Defines the `NonDet` type and `nondet!` macro for tracking non-determinism.
2//!
3//! All **safe** APIs in Hydro guarantee determinism, even in the face of networking delays
4//! and concurrency across machines. But often it is necessary to do something non-deterministic,
5//! like generate events at a fixed wall-clock-time interval, or split an input into arbitrarily
6//! sized batches.
7//!
8//! These non-deterministic APIs take additional parameters called **non-determinism guards**.
9//! These values, with type `NonDet`, help you reason about how non-determinism affects your
10//! application. To pass a non-determinism guard, you must invoke `nondet!()` with an explanation
11//! for how the non-determinism affects the application.
12//!
13//! See the [Hydro docs](https://hydro.run/docs/hydro/reference/correctness/nondet) for more.
14
15/// A non-determinism guard, which documents how a source of non-determinism affects the application.
16///
17/// To create a non-determinism guard, use the [`nondet!`] macro, which takes in a doc comment
18/// explaining the effects of the particular source of non-determinism, and additional
19/// non-determinism guards that justify the form of non-determinism.
20///
21/// The `H` type parameter is the **simulator hook payload** the guard can carry (see
22/// `hydro_lang::sim::hooks`), and defaults to `()` so that plain `NonDet` means what it
23/// always did. A hook lets a simulation test take manual control of the non-deterministic
24/// decisions guarded by this value:
25///
26/// - An unsafe *operator* (like `batch`) takes a guard with an optional handle for that
27///   one operator, e.g. `NonDet<Option<BatchHook<T>>>`. A component that declares a
28///   parameter of exactly this type passes it **directly** to the operator it controls,
29///   documenting it in a `# Non-Determinism` section of its Rustdoc.
30/// - A *component* that contains several unsafe operators can expose them all through one
31///   guard by using a tuple payload, e.g. `NonDet<(Option<BatchHook<T>>,
32///   Option<SnapshotHook<S>>)>`, letting the test hook some, all, or none of them. The
33///   component splits the payload with [`NonDet::take_hook`] and attaches each part to
34///   the operator it controls via `nondet!(... hook = part)`.
35///
36/// Payload types implement [`Default`] ("no hook"), which is what `nondet!(...)` without
37/// a `hook =` argument produces.
38#[derive(Copy, Clone)]
39pub struct NonDet<H = ()> {
40    hook: H,
41}
42
43impl<H> NonDet<H> {
44    /// Creates a guard with no hook attached. Use the [`nondet!`] macro instead of calling
45    /// this directly, so that the reason for the non-determinism is documented.
46    #[doc(hidden)]
47    pub fn unhooked() -> Self
48    where
49        H: Default,
50    {
51        NonDet { hook: H::default() }
52    }
53
54    /// Creates a guard carrying the given hook payload. Use the [`nondet!`] macro's
55    /// `hook = ...` argument instead of calling this directly.
56    #[doc(hidden)]
57    pub fn hooked(hook: impl Into<H>) -> Self {
58        NonDet { hook: hook.into() }
59    }
60
61    /// Takes the hook payload out of this guard, leaving the default ("no hook") payload
62    /// in place.
63    ///
64    /// This is how a component splits a *composite* guard across the unsafe operators it
65    /// contains: take the payload once, destructure it, and attach each part to the
66    /// operator it controls via `nondet!(... hook = part)`. (A guard whose type already
67    /// matches a single operator's parameter is simply passed to that operator directly.)
68    /// Taking the payload (rather than copying it) ensures a hook is only ever bound where
69    /// the binding is visible — re-wrapping a guard with `nondet!` never propagates a
70    /// binding.
71    pub fn take_hook(&mut self) -> H
72    where
73        H: Default,
74    {
75        std::mem::take(&mut self.hook)
76    }
77}
78
79#[doc(inline)]
80pub use crate::__nondet__ as nondet;
81
82#[macro_export]
83/// Fulfills a non-determinism guard parameter by declaring a reason why the
84/// non-determinism is tolerated or providing other non-determinism guards
85/// that forward the inner non-determinism.
86///
87/// The first argument must be a doc comment with the reason the non-determinism
88/// is okay. If forwarding a parent non-determinism, because the non-determinism
89/// is not handled internally, you should provide a short explanation of how the
90/// inner non-determinism is captured by the outer one. If the non-determinism
91/// is locally resolved, you should document _why_ this is the case.
92///
93/// An optional trailing `hook = ...` argument attaches a **simulator hook payload** to
94/// the guard (see `hydro_lang::sim::hooks`), letting a simulation test script the
95/// decisions of the unsafe operator(s) that consume the guard. The expression is
96/// converted with [`Into`], so a raw handle can be passed where an optional one is
97/// expected:
98///
99/// ```rust,ignore
100/// nondet!(/** reason */)                        // no hook attached (the payload default)
101/// nondet!(/// reason
102///         nondet_parent)                        // forwarded justification, no hook
103/// nondet!(/** reason */ hook = my_hook)         // attach a hook handle
104/// nondet!(/** reason */ hook = part)            // attach a payload split off a composite
105///                                               // guard with `NonDet::take_hook`
106/// nondet!(/** reason */ hook = (h1.into(), None)) // composite payload, hooking only `h1`
107/// ```
108///
109/// Note that forwarding a guard *without* `hook =` never propagates a hook binding, even
110/// if the forwarded guard carries one; every binding is visible at the exact
111/// operator it controls. A guard whose type already matches an operator's parameter is
112/// passed to that operator directly; to attach a hook received as part of a composite
113/// payload, split it off explicitly with
114/// [`NonDet::take_hook`](crate::nondet::NonDet::take_hook) and pass it via `hook =`.
115///
116/// # Examples
117/// Locally resolved non-determinism:
118/// ```rust,no_run
119/// # use hydro_lang::prelude::*;
120/// use std::time::Duration;
121///
122/// # #[cfg(feature = "tokio")]
123/// fn singleton_with_delay<T, L>(
124///   singleton: Singleton<T, Process<L>, Unbounded>
125/// ) -> Optional<T, Process<L>, InitNone> {
126///   singleton
127///     .sample_every(q!(Duration::from_secs(1)), nondet!(/**
128///         non-deterministic samples will eventually resolve to stable result
129///     */))
130///     .last()
131///     .into()
132/// }
133/// ```
134///
135/// Forwarded non-determinism:
136/// ```rust
137/// # use hydro_lang::prelude::*;
138/// use std::fmt::Debug;
139/// use std::time::Duration;
140///
141/// use hydro_lang::live_collections::stream::ExactlyOnce;
142///
143/// /// ...
144/// ///
145/// /// # Non-Determinism
146/// /// - `nondet_samples`: this function will non-deterministically print elements
147/// ///   from the stream according to a timer
148/// # #[cfg(feature = "tokio")]
149/// fn print_samples<T: Debug, L>(
150///     stream: Stream<T, Process<L>, Unbounded>,
151///     nondet_samples: NonDet,
152/// ) {
153///     stream
154///         .sample_every(
155///             q!(Duration::from_secs(1)),
156///             nondet!(
157///                 /// non-deterministic timing will result in non-determistic samples printed
158///                 nondet_samples
159///             ),
160///         )
161///         .assume_retries::<ExactlyOnce>(nondet!(
162///             /// non-deterministic duplicated logs are okay
163///             nondet_samples
164///         ))
165///         .for_each(q!(|v| println!("Sample: {:?}", v)))
166/// }
167/// ```
168macro_rules! __nondet__ {
169    ($(#[doc = $doc:expr])+ hook = $hook:expr $(,)?) => {
170        $crate::nondet::NonDet::hooked($hook)
171    };
172    ($(#[doc = $doc:expr])+$($forward:ident),*) => {
173        {
174            $(let _ = $forward;)*
175            $crate::nondet::NonDet::unhooked()
176        }
177    };
178}