Skip to main content

logicaffeine_runtime/
payload.rs

1//! `RtPayload` — the `Send`-able value subset that crosses task (and OS-thread)
2//! boundaries via channels.
3//!
4//! The interpreter and VM heaps are `Rc`-based (`!Send`). A value moved through a
5//! channel is *materialized* into this owned, allocation-self-contained form on
6//! the sending side and *rebuilt* into the receiver's heap on the other side.
7//! The marshalling between `RuntimeValue` / the VM `Value` and `RtPayload` lives
8//! in `logicaffeine-compile` (which knows those representations); this crate only
9//! defines the wire shape and guarantees it is `Send`, which is what makes the
10//! M:N work-stealing driver sound.
11
12/// A self-contained, `Send` value that can move between tasks and threads.
13///
14/// This deliberately excludes `Rc`/`RefCell`-backed and closure values: those
15/// either are not `Send` or would alias another task's heap. CRDT shared cells
16/// (which are `Arc`-backed and shared rather than moved) get their own variant
17/// when the scheduler grows them in a later phase.
18#[derive(Debug, Clone, PartialEq)]
19pub enum RtPayload {
20    /// The unit / absence value.
21    Nothing,
22    Int(i64),
23    /// An exact integer that does not fit `i64`, carried as its sign + little-endian
24    /// magnitude bytes — a dependency-free, `Send` form of `BigInt` (reconstructed
25    /// with `BigInt::from_le_bytes` on the far side).
26    BigInt { negative: bool, magnitude: Vec<u8> },
27    /// An exact rational: the signed numerator and the (always positive) denominator,
28    /// each as little-endian magnitude bytes — a dependency-free, `Send` form of
29    /// `Rational` (reconstructed with `Rational::new` on the far side). `1/3` survives
30    /// here exactly, where a JSON `0.333…` would round.
31    Rational { num_negative: bool, num_magnitude: Vec<u8>, den_magnitude: Vec<u8> },
32    /// An exact base-10 fixed-point number (money): its signed coefficient as
33    /// little-endian magnitude bytes plus the base-10 `scale` (decimal places) — a
34    /// dependency-free, `Send` form of `Decimal` (reconstructed with
35    /// `Decimal::from_le_bytes` on the far side). `19.99` survives exactly, scale and all.
36    Decimal { negative: bool, magnitude: Vec<u8>, scale: u32 },
37    /// An exact monetary amount: its `Decimal` amount (sign + LE magnitude + base-10 scale) plus the
38    /// ISO-4217 currency code. Reconstructed as `Money { Decimal::from_le_bytes, currency::by_code }`.
39    Money { negative: bool, magnitude: Vec<u8>, scale: u32, currency: String },
40    /// A 128-bit UUID — its 16 big-endian bytes verbatim. Reconstructed with `Uuid::from_bytes`.
41    Uuid([u8; 16]),
42    /// An exact complex number `re + im·i`: each part a rational (signed numerator + a
43    /// positive denominator) as little-endian magnitude bytes. Reconstructed with
44    /// `Complex::new` on the far side; `i·i = −1` survives exactly.
45    Complex {
46        re_negative: bool,
47        re_num: Vec<u8>,
48        re_den: Vec<u8>,
49        im_negative: bool,
50        im_num: Vec<u8>,
51        im_den: Vec<u8>,
52    },
53    /// An element of ℤ/nℤ: its (non-negative) residue and modulus as little-endian magnitude
54    /// bytes. Reconstructed with `Modular::new` on the far side; the ring is preserved.
55    Modular { value: Vec<u8>, modulus: Vec<u8> },
56    /// A dimensioned physical quantity: its SI-base magnitude as an exact rational (signed
57    /// numerator + positive denominator, little-endian bytes), its dimension as the exponent
58    /// vector (`dim_num[i]/dim_den[i]` over the base axes), and the display unit's symbol. The far
59    /// side rebuilds the magnitude with `Rational`, the dimension with `Dimension::from_exps`, and
60    /// resolves the unit by symbol (falling back to the SI/dimension display for compound units).
61    Quantity {
62        num_negative: bool,
63        num_magnitude: Vec<u8>,
64        den_magnitude: Vec<u8>,
65        dim_num: Vec<i32>,
66        dim_den: Vec<i32>,
67        unit_symbol: String,
68    },
69    Float(f64),
70    Bool(bool),
71    Char(char),
72    /// An owned string (not an `Rc<str>`).
73    Text(String),
74    /// A fully-materialized sequence.
75    List(Vec<RtPayload>),
76    /// A fixed heterogeneous tuple.
77    Tuple(Vec<RtPayload>),
78    /// A set, materialized as its elements.
79    Set(Vec<RtPayload>),
80    /// A map, materialized as key/value pairs.
81    Map(Vec<(RtPayload, RtPayload)>),
82    /// A struct instance: its type name and named fields.
83    Struct {
84        type_name: String,
85        fields: Vec<(String, RtPayload)>,
86    },
87    /// An inductive (sum-type) value: its type, constructor, and arguments.
88    Inductive {
89        type_name: String,
90        constructor: String,
91        args: Vec<RtPayload>,
92    },
93    /// A duration, carried in its base unit.
94    Duration(i64),
95    /// A calendar date.
96    Date(i32),
97    /// A moment in time.
98    Moment(i64),
99    /// A calendar span (months + days).
100    Span { months: i32, days: i32 },
101    /// A time-of-day.
102    Time(i64),
103    /// A fixed-width wrapping integer (`Word32`/`Word64`): its bit width (32 or 64) and the
104    /// value zero-extended to `u64`. Reconstructed via `WordVal::from_u64` on the far side.
105    Word { width: u32, bits: u64 },
106    /// A channel handle (a `Pipe`) — an opaque scheduler token. `Send` (just an
107    /// id), so a channel can be passed as a spawn argument across worker threads;
108    /// the receiving task resolves it against the one shared scheduler.
109    Chan(crate::channel::ChanId),
110    /// A spawned-task handle — likewise an opaque `Send` scheduler token.
111    TaskHandle(crate::task::TaskId),
112    /// A remote-peer handle — its canonical relay topic. A `String` is trivially
113    /// `Send`, so a peer can be passed as a spawn argument across worker threads.
114    Peer(String),
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    fn assert_send<T: Send>() {}
122
123    #[test]
124    fn rtpayload_is_send() {
125        // Compile-time guarantee: payloads can cross thread boundaries. This is
126        // the property the M:N work-stealing driver depends on (only `RtPayload`
127        // and small ids ever cross a worker boundary).
128        assert_send::<RtPayload>();
129    }
130
131    #[test]
132    fn rtpayload_roundtrips_structurally() {
133        let v = RtPayload::Struct {
134            type_name: "Point".into(),
135            fields: vec![
136                ("x".into(), RtPayload::Int(1)),
137                (
138                    "y".into(),
139                    RtPayload::List(vec![RtPayload::Bool(true), RtPayload::Text("hi".into())]),
140                ),
141            ],
142        };
143        assert_eq!(v.clone(), v);
144    }
145
146    #[test]
147    fn rtpayload_nested_collections() {
148        let m = RtPayload::Map(vec![
149            (RtPayload::Text("a".into()), RtPayload::Int(1)),
150            (RtPayload::Text("b".into()), RtPayload::Set(vec![RtPayload::Char('x')])),
151        ]);
152        match m {
153            RtPayload::Map(entries) => assert_eq!(entries.len(), 2),
154            _ => panic!("expected map"),
155        }
156    }
157}