Skip to main content

origin/
lib.rs

1//! # Origin
2//!
3//! Every value is either in the interior or at a boundary.
4//! The compiler knows which. One pattern. Every domain.
5
6use std::fmt;
7
8// Re-export proc-macros so users write `use origin::BoundaryKind;`
9pub use origin_macros::BoundaryKind;
10pub use origin_macros::boundary_check;
11
12/// A boundary reason. Every domain defines its own.
13pub trait BoundaryKind: fmt::Debug + Send + Sync + 'static {}
14
15/// The core type. Interior or Boundary. One distinction. Every domain.
16#[derive(Debug)]
17#[must_use = "this Value may be a Boundary that must be handled"]
18pub enum Value<T, B: BoundaryKind = GenericBoundary> {
19    /// Interior: the value is in safe territory.
20    Interior(T),
21    /// Boundary: the value crossed the edge. Carries the reason and last known value.
22    Boundary {
23        reason: B,
24        last: T,
25    },
26}
27
28impl<T, B: BoundaryKind> Value<T, B> {
29    /// Construct an interior value.
30    pub fn interior(value: T) -> Self {
31        Value::Interior(value)
32    }
33
34    /// Construct a boundary value with a reason and last known value.
35    pub fn boundary(reason: B, last: T) -> Self {
36        Value::Boundary { reason, last }
37    }
38
39    pub fn is_interior(&self) -> bool {
40        matches!(self, Value::Interior(_))
41    }
42
43    pub fn is_boundary(&self) -> bool {
44        matches!(self, Value::Boundary { .. })
45    }
46
47    /// Unwrap the interior value, or return a fallback.
48    pub fn or(self, fallback: T) -> T {
49        match self {
50            Value::Interior(v) => v,
51            Value::Boundary { .. } => fallback,
52        }
53    }
54
55    /// Unwrap the interior value, or panic.
56    pub fn unwrap(self) -> T {
57        match self {
58            Value::Interior(v) => v,
59            Value::Boundary { reason, .. } => {
60                panic!("called unwrap on a Boundary: {reason:?}")
61            }
62        }
63    }
64
65    /// Map over the interior value. Boundaries propagate unchanged.
66    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Value<U, B> {
67        match self {
68            Value::Interior(v) => Value::Interior(f(v)),
69            Value::Boundary { reason, last } => Value::Boundary {
70                reason,
71                last: f(last),
72            },
73        }
74    }
75
76    /// Extract the interior value, or return the boundary reason as `Err`
77    /// for `?` propagation. The residual is not carried through propagation —
78    /// match explicitly to access it.
79    pub fn propagate(self) -> Result<T, B> {
80        match self {
81            Value::Interior(v) => Ok(v),
82            Value::Boundary { reason, .. } => Err(reason),
83        }
84    }
85
86    /// Extract the interior value while recording the step in a trace.
87    /// If boundary, records the residual in the chain and returns the reason.
88    /// If interior, records the value and continues.
89    pub fn trace(self, chain: &mut Chain, label: &'static str) -> Result<T, B>
90    where
91        T: std::fmt::Debug,
92    {
93        match self {
94            Value::Interior(v) => {
95                chain.record(label, &v);
96                Ok(v)
97            }
98            Value::Boundary { reason, last } => {
99                chain.record_boundary(label, &last);
100                Err(reason)
101            }
102        }
103    }
104
105    /// Chain operations that may themselves hit a boundary.
106    /// Requires U: Default so a boundary can carry a last value when types change.
107    pub fn and_then<U: Default>(self, f: impl FnOnce(T) -> Value<U, B>) -> Value<U, B> {
108        match self {
109            Value::Interior(v) => f(v),
110            Value::Boundary { reason, .. } => Value::Boundary {
111                reason,
112                last: U::default(),
113            },
114        }
115    }
116}
117
118// -- Residual chain --
119
120/// A step in the residual chain: what was computed at each layer.
121#[derive(Debug, Clone)]
122pub struct ChainEntry {
123    /// Which layer produced this value.
124    pub label: &'static str,
125    /// Debug representation of the value at this layer.
126    pub value: String,
127    /// Whether this was the layer that hit the boundary.
128    pub is_boundary: bool,
129}
130
131/// The residual chain: every step of how you got there.
132///
133/// When a pipeline propagates through `?`, each layer records what it
134/// computed. If a boundary is reached, the chain shows the full path —
135/// not just where you stopped, but every step on the way.
136#[derive(Debug, Clone, Default)]
137pub struct Chain {
138    entries: Vec<ChainEntry>,
139}
140
141impl Chain {
142    pub fn new() -> Self {
143        Chain { entries: Vec::new() }
144    }
145
146    /// Record a successful interior step.
147    pub fn record(&mut self, label: &'static str, value: &dyn fmt::Debug) {
148        self.entries.push(ChainEntry {
149            label,
150            value: format!("{value:?}"),
151            is_boundary: false,
152        });
153    }
154
155    /// Record the residual at the boundary — the last step before propagation.
156    pub fn record_boundary(&mut self, label: &'static str, value: &dyn fmt::Debug) {
157        self.entries.push(ChainEntry {
158            label,
159            value: format!("{value:?}"),
160            is_boundary: true,
161        });
162    }
163
164    /// The entries in order, from first layer to the boundary.
165    pub fn entries(&self) -> &[ChainEntry] {
166        &self.entries
167    }
168
169    /// How many layers completed before the boundary.
170    pub fn depth(&self) -> usize {
171        self.entries.iter().filter(|e| !e.is_boundary).count()
172    }
173}
174
175impl fmt::Display for Chain {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        for (i, entry) in self.entries.iter().enumerate() {
178            let marker = if entry.is_boundary { "✗" } else { "✓" };
179            write!(f, "  {marker} [{}] {}: {}", i + 1, entry.label, entry.value)?;
180            if i < self.entries.len() - 1 {
181                writeln!(f)?;
182            }
183        }
184        Ok(())
185    }
186}
187
188// -- Built-in boundary kinds --
189
190#[derive(Debug, Clone)]
191pub struct GenericBoundary;
192impl BoundaryKind for GenericBoundary {}
193
194// Math
195#[derive(Debug, Clone)]
196pub struct DivisionByZero;
197impl BoundaryKind for DivisionByZero {}
198
199#[derive(Debug, Clone)]
200pub struct Overflow;
201impl BoundaryKind for Overflow {}
202
203#[derive(Debug, Clone)]
204pub struct Underflow;
205impl BoundaryKind for Underflow {}
206
207#[derive(Debug, Clone)]
208pub struct Imaginary { pub real_part: f64 }
209impl BoundaryKind for Imaginary {}
210
211// AI inference
212#[derive(Debug, Clone)]
213pub struct ModelUncertain { pub confidence: f64 }
214impl BoundaryKind for ModelUncertain {}
215
216#[derive(Debug, Clone)]
217pub struct Hallucinated { pub confidence: f64, pub grounding: Option<String> }
218impl BoundaryKind for Hallucinated {}
219
220#[derive(Debug, Clone)]
221pub struct Contradicted { pub sources: Vec<String>, pub claim: String }
222impl BoundaryKind for Contradicted {}
223
224// -- Demonstration functions --
225//
226// These show the shape of Value<T, B>. They are teaching examples,
227// not domain rules. `divide` catches exact zero — IEEE 754 handles
228// near-zero differently (it returns infinity). If your domain cares
229// about near-zero, define your own boundary kind and threshold.
230// That's the point: Origin gives you the mechanism, you define
231// what "boundary" means for your domain.
232
233/// Division that catches exact zero. A demonstration of Value<T, B>.
234pub fn divide(a: f64, b: f64) -> Value<f64, DivisionByZero> {
235    if b == 0.0 {
236        Value::Boundary { reason: DivisionByZero, last: a }
237    } else {
238        Value::Interior(a / b)
239    }
240}
241
242/// Square root that catches negative input. A demonstration of Value<T, B>.
243pub fn sqrt(a: f64) -> Value<f64, Imaginary> {
244    if a < 0.0 {
245        Value::Boundary { reason: Imaginary { real_part: a }, last: 0.0 }
246    } else {
247        Value::Interior(a.sqrt())
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn division_interior() {
257        let result = divide(10.0, 2.0);
258        assert!(result.is_interior());
259        assert_eq!(result.unwrap(), 5.0);
260    }
261
262    #[test]
263    fn division_boundary() {
264        let result = divide(10.0, 0.0);
265        assert!(result.is_boundary());
266        match result {
267            Value::Boundary { reason: DivisionByZero, last } => {
268                assert_eq!(last, 10.0);
269            }
270            _ => panic!("expected boundary"),
271        }
272    }
273
274    #[test]
275    fn division_or_fallback() {
276        assert_eq!(divide(10.0, 0.0).or(0.0), 0.0);
277    }
278
279    #[test]
280    fn sqrt_interior() {
281        assert_eq!(sqrt(9.0).unwrap(), 3.0);
282    }
283
284    #[test]
285    fn sqrt_boundary() {
286        let result = sqrt(-1.0);
287        assert!(result.is_boundary());
288    }
289
290    #[test]
291    fn map_propagates_boundary() {
292        let result = divide(10.0, 0.0).map(|v| v * 2.0);
293        assert!(result.is_boundary());
294    }
295
296    #[test]
297    fn map_transforms_interior() {
298        let result = divide(10.0, 2.0).map(|v| v * 2.0);
299        assert_eq!(result.unwrap(), 10.0);
300    }
301
302    #[test]
303    fn model_uncertain() {
304        let result: Value<String, ModelUncertain> = Value::Boundary {
305            reason: ModelUncertain { confidence: 0.3 },
306            last: "Paris is the capital of France".to_string(),
307        };
308        assert!(result.is_boundary());
309    }
310
311    #[test]
312    fn hallucinated() {
313        let result: Value<String, Hallucinated> = Value::Boundary {
314            reason: Hallucinated { confidence: 0.1, grounding: None },
315            last: "The moon is made of cheese".to_string(),
316        };
317        match result {
318            Value::Boundary { reason, .. } => assert!(reason.grounding.is_none()),
319            _ => panic!("expected boundary"),
320        }
321    }
322
323    #[test]
324    fn and_then_interior() {
325        let result = divide(10.0, 2.0).and_then(|v| divide(v, 2.0));
326        assert!(result.is_interior());
327        assert_eq!(result.unwrap(), 2.5);
328    }
329
330    #[test]
331    fn and_then_propagates_boundary() {
332        let result = divide(10.0, 0.0).and_then(|v| divide(v, 2.0));
333        assert!(result.is_boundary());
334    }
335
336    // -- boundary_check macro tests --
337
338    #[boundary_check]
339    fn safe_divide(a: f64, b: f64) -> f64 {
340        divide(a, b).or(0.0)
341    }
342
343    #[test]
344    fn boundary_check_allows_or() {
345        assert_eq!(safe_divide(10.0, 2.0), 5.0);
346        assert_eq!(safe_divide(10.0, 0.0), 0.0);
347    }
348
349    #[boundary_check]
350    fn safe_match(a: f64, b: f64) -> f64 {
351        match divide(a, b) {
352            Value::Interior(v) => v,
353            Value::Boundary { reason: DivisionByZero, last } => last,
354        }
355    }
356
357    #[test]
358    fn boundary_check_allows_match() {
359        assert_eq!(safe_match(10.0, 2.0), 5.0);
360        assert_eq!(safe_match(10.0, 0.0), 10.0);
361    }
362
363    // -- derive(BoundaryKind) test --
364
365    #[derive(Debug, Clone, BoundaryKind)]
366    #[boundary_kind(crate_path = "crate")]
367    struct InsufficientFunds {
368        balance: f64,
369        required: f64,
370    }
371
372    #[test]
373    fn user_defined_boundary() {
374        let result: Value<f64, InsufficientFunds> = Value::Boundary {
375            reason: InsufficientFunds { balance: 50.0, required: 100.0 },
376            last: 50.0,
377        };
378        assert!(result.is_boundary());
379        match result {
380            Value::Boundary { reason, .. } => {
381                assert_eq!(reason.balance, 50.0);
382                assert_eq!(reason.required, 100.0);
383            }
384            _ => panic!("expected boundary"),
385        }
386    }
387}