Skip to main content

code_native/
lib.rs

1//! Safe(r) Rust bindings for writing a native module — `.so` or `.a` — for
2//! the [Code programming language](https://github.com/codelovesme/code).
3//!
4//! `code_abi.h`'s contract needs two things from a module: agreement on the
5//! `CodeValue` wire layout, and a `code_release` (plus friends) built from
6//! the *real* `runtime.c` rather than a reimplementation that merely looks
7//! compatible — getting refcounting subtly wrong is the kind of bug that
8//! corrupts memory rather than crashing where you'd notice. This crate's
9//! `build.rs` compiles the vendored `runtime.c` and links it into your
10//! `cdylib` directly, so every function below calls the same code the host
11//! runtime and every C module trust. (A `.a` module wants the opposite —
12//! the host already has the one runtime there — which is what the
13//! `static-module` feature turns off; see this crate's README.)
14//!
15//! # Quick start
16//!
17//! ```rust,ignore
18//! use code_native::*;
19//!
20//! #[no_mangle]
21//! pub extern "C" fn code_module_abi_version() -> u32 {
22//!     CODE_ABI_VERSION
23//! }
24//!
25//! #[no_mangle]
26//! pub unsafe extern "C" fn code_module_dispatch(out: *mut CodeValue, particle: *const CodeValue) {
27//!     let particle = &*particle;
28//!     match read_field_str(particle, "_class") {
29//!         Some("Double") => {
30//!             let value = read_field_number(particle, "value").unwrap_or(0.0);
31//!             make_result(&mut *out, "DoubleResult", |slot| code_number(slot, value * 2.0));
32//!         }
33//!         // A class this module does not handle answers null — see
34//!         // docs/todo/errors-as-particles.md.
35//!         _ => null(&mut *out),
36//!     }
37//! }
38//! ```
39//!
40//! Build with `crate-type = ["cdylib"]`, then `link "libmymodule.so" as m`
41//! from `.code` source. See this crate's README for the full walkthrough,
42//! including `.a` static modules and `code_module_vars`.
43//!
44//! To *speak first* rather than only answer — pushing particles into the
45//! program, which is what `Log`/`Exception`/`Tick`-shaped traffic needs —
46//! add [`declare_inbound!`] and call [`emit_inbound`]:
47//!
48//! ```rust,ignore
49//! code_native::declare_inbound!();
50//!
51//! fn report(message: &str) {
52//!     let mut p = CodeValue::zeroed();
53//!     // ... build a particle ...
54//!     emit_inbound(&p);
55//!     release(&mut p);
56//! }
57//! ```
58//!
59//! A pushed class the program has no handler for is dropped, so a module may
60//! report without every program that links it having to listen.
61//!
62//! `code_module_dispatch` and `code_module_abi_version` are the two required
63//! exports — there is no macro generating them here (unlike the *old*
64//! language's `code-native`): the new ABI dropped the descriptor-table
65//! design for one function a module dispatches through itself, so there is
66//! no boilerplate left to generate. `code_release` needs no Rust code at
67//! all — it comes from the linked `runtime.c` object automatically.
68
69use std::ffi::{c_char, c_int, c_void, CStr};
70use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
71
72// ===========================================================================
73// Wire layout — bit-for-bit `code_abi.h`. Only the pointer/int/float shapes
74// matter for ABI compatibility (not what they're named), but names are kept
75// identical to the header so the two are trivially diffable.
76// ===========================================================================
77
78/// Current ABI version. A module's `code_module_abi_version` must return
79/// this.
80pub const CODE_ABI_VERSION: u32 = 1;
81
82/// Byte stride of an array/object element buffer — **not** `size_of::<CodeValue>()`.
83/// This is a frozen ABI constant with headroom for `CodeValue` to grow
84/// without breaking already-compiled modules; always address a buffer
85/// through [`slot_at`], never by casting to `*mut CodeValue` and indexing.
86pub const CODE_VALUE_SLOT_SIZE: usize = 80;
87
88#[repr(C)]
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum CodeTag {
91    Number,
92    Str,
93    Bool,
94    Null,
95    Array,
96    Object,
97}
98
99#[repr(C)]
100pub struct CodeValue {
101    pub tag: CodeTag,
102    pub heap: c_int,
103    pub number: f64,
104    pub str: *const c_char,
105    pub boolean: c_int,
106    /// `CODE_ARRAY`: element buffer; `CODE_OBJECT`: value buffer — both
107    /// strided at [`CODE_VALUE_SLOT_SIZE`], addressed via [`slot_at`].
108    pub items: *mut c_void,
109    /// `CODE_OBJECT` only, parallel to `items`.
110    pub keys: *mut *const c_char,
111    pub len: i64,
112}
113
114impl CodeValue {
115    /// An all-zero value — tag `Number`, `0.0`, not heap-owned. Bit-for-bit
116    /// what `CodeValue x = {0};` produces in C, and the required starting
117    /// state before passing `&mut` to any constructor below (each one calls
118    /// `code_release` on `out` first, exactly like the C ABI expects).
119    pub fn zeroed() -> Self {
120        // SAFETY: an all-zero-bytes CodeValue is a valid Number(0.0), which
121        // `code_release` (called by every constructor before overwriting
122        // `out`) already treats as a safe no-op — the same invariant `{0}`
123        // relies on in every C module.
124        unsafe { std::mem::zeroed() }
125    }
126}
127
128impl Default for CodeValue {
129    fn default() -> Self {
130        Self::zeroed()
131    }
132}
133
134#[repr(C)]
135pub struct CodeVarList {
136    pub count: i64,
137    pub names: *const *const c_char,
138    /// `CODE_VALUE_SLOT_SIZE` stride, `count` slots — see [`slot_at`].
139    pub values: *mut CodeValue,
140}
141
142// Both types carry raw pointers, so Rust doesn't derive Send/Sync for them
143// automatically — but `code_module_vars` (see README) is exactly the case
144// that needs a `static`/`OnceLock<CodeVarList>`, and the host only ever
145// reads this data (once, at `link` time), never mutates it concurrently.
146// Matches the old language's own `code-abi` crate, which needed the same
147// impls for the same reason.
148unsafe impl Send for CodeValue {}
149unsafe impl Sync for CodeValue {}
150unsafe impl Send for CodeVarList {}
151unsafe impl Sync for CodeVarList {}
152
153// ===========================================================================
154// Raw bindings to `runtime.c`'s exported (non-`static`) functions — the same
155// symbols `code_abi.h` declares for a C module. Calling into the actual
156// compiled `runtime.c`, not a port of it, is what keeps this crate free of
157// the layout-drift risk the *old* language's `code-native`/`code-abi` pair
158// needed a dedicated test to guard against.
159// ===========================================================================
160
161extern "C" {
162    fn code_number(out: *mut CodeValue, n: f64);
163    fn code_str(out: *mut CodeValue, s: *const c_char);
164    fn code_bool(out: *mut CodeValue, b: c_int);
165    fn code_null(out: *mut CodeValue);
166    fn code_array(out: *mut CodeValue, items: *mut c_void, len: i64);
167    fn code_object(out: *mut CodeValue, keys: *mut *const c_char, values: *mut c_void, len: i64);
168    fn code_copy(out: *mut CodeValue, src: *const CodeValue);
169    fn code_retain(v: *const CodeValue);
170    fn code_values_equal(a: *const CodeValue, b: *const CodeValue) -> c_int;
171    fn code_runtime_error(message: *const c_char) -> !;
172
173    // `build.rs` compiles `runtime.c` with `code_release` renamed to this at
174    // the preprocessor level (`-D`), and this crate re-exports it below under
175    // the real name from a function rustc actually treats as part of the
176    // crate (not an archive) — see that `#[no_mangle]` fn's own doc comment
177    // for why the rename is needed at all.
178    #[cfg(feature = "shared-module")]
179    fn code_native_vendored_release(v: *mut CodeValue);
180}
181
182// A `.a` module links against the *host's* runtime, which already defines
183// `code_release` under its real name — so there is nothing to rename and
184// nothing to re-export, and `release` below calls it directly.
185#[cfg(not(feature = "shared-module"))]
186extern "C" {
187    fn code_release(v: *mut CodeValue);
188}
189
190/// The ABI's required `code_release` export. Defined here, as a real Rust
191/// function, rather than left as whatever `runtime.c`'s own `code_release`
192/// would otherwise be: `cdylib` targets get `--exclude-libs=ALL` from
193/// rustc by default, which hides every symbol pulled in from a *linked
194/// static archive* (exactly what `build.rs`'s `cc::Build::compile` produces
195/// from `runtime.c`) out of the shared library's dynamic symbol table —
196/// even though this crate's own code calls it just fine internally. A
197/// symbol the crate defines directly (this function) isn't subject to that
198/// exclusion, so renaming the archive's copy and re-exporting it from here
199/// is what makes the host's `dlsym("code_release")` actually find it.
200///
201/// # Safety
202/// `v` must point to a valid, initialized `CodeValue` — the same
203/// requirement `runtime.c`'s own `code_release` has. The host only ever
204/// calls this on values it deep-copied out of your `code_module_dispatch`
205/// result, so you should never need to call it yourself except via
206/// [`release`].
207#[cfg(feature = "shared-module")]
208#[no_mangle]
209pub unsafe extern "C" fn code_release(v: *mut CodeValue) {
210    code_native_vendored_release(v)
211}
212
213/// Addresses slot `index` of a [`CODE_VALUE_SLOT_SIZE`]-strided buffer —
214/// the Rust equivalent of `code_abi.h`'s `code_slot_at`. Pure pointer
215/// arithmetic, safe to reimplement independently (no allocator/refcount
216/// logic to drift from `runtime.c`).
217pub fn slot_at(base: *mut c_void, index: i64) -> *mut CodeValue {
218    (base as *mut u8).wrapping_offset(index as isize * CODE_VALUE_SLOT_SIZE as isize)
219        as *mut CodeValue
220}
221
222fn cstr(s: &str) -> std::ffi::CString {
223    std::ffi::CString::new(s).unwrap_or_else(|_| std::ffi::CString::new("<invalid-utf8>").unwrap())
224}
225
226// ===========================================================================
227// Safe scalar constructors — thin wrappers: `code_release`s `out` first
228// (matching every `runtime.c` constructor's own contract), then delegates.
229// ===========================================================================
230
231/// Write a Number into `out`.
232pub fn number(out: &mut CodeValue, n: f64) {
233    unsafe { code_number(out, n) }
234}
235
236/// Write a Str into `out`, borrowing `s` for `'static` (a string literal or
237/// otherwise permanently-alive buffer) rather than copying it — matching
238/// `code_str`'s own borrowing contract. Use [`owned_str`] for a value built
239/// at runtime that needs its own heap block.
240pub fn borrowed_str(out: &mut CodeValue, s: &'static CStr) {
241    unsafe { code_str(out, s.as_ptr()) }
242}
243
244/// Write a Str into `out` from a freshly-built Rust string. Leaks the
245/// `CString` — acceptable here because the value crosses into the host's
246/// own heap the moment your `code_module_dispatch` returns (the host
247/// deep-copies your result and then calls your module's `code_release` on
248/// it, which only ever frees what `runtime.c`'s own allocator built, never
249/// this leaked buffer).
250pub fn owned_str(out: &mut CodeValue, s: &str) {
251    let c = cstr(s);
252    unsafe { code_str(out, c.as_ptr()) }
253    std::mem::forget(c);
254}
255
256/// Write a Bool into `out`.
257pub fn boolean(out: &mut CodeValue, b: bool) {
258    unsafe { code_bool(out, b as c_int) }
259}
260
261/// Write Null into `out`.
262pub fn null(out: &mut CodeValue) {
263    unsafe { code_null(out) }
264}
265
266/// Release whatever `v` holds — call on every temporary [`CodeValue`] you
267/// built and no longer need (matching `runtime.c`'s own refcounting rule:
268/// every slot that ever named a heap block owns exactly one reference to
269/// it).
270pub fn release(v: &mut CodeValue) {
271    unsafe { code_release(v) }
272}
273
274/// Deep-copy `src` into `out` — `out` ends up owning its own references to
275/// everything `src` points at, and `src` is left untouched. This is how a
276/// handler passes a value it did not build itself along (e.g. an `Echo`
277/// returning its operand): the copy takes new references, so neither side's
278/// lifetime constrains the other.
279pub fn copy(out: &mut CodeValue, src: &CodeValue) {
280    unsafe { code_copy(out, src) }
281}
282
283/// Increment `v`'s refcount — needed only if you're holding onto a
284/// [`CodeValue`] you didn't just build yourself (e.g. a borrowed field from
285/// [`find_field`]) somewhere that will outlive the call it came from.
286/// Every retained value must be balanced by a [`release`].
287pub fn retain(v: &CodeValue) {
288    unsafe { code_retain(v) }
289}
290
291/// `obj.field` field access. **Total**: a missing field, or an `obj` that is
292/// not an Object at all, writes Null into `out`.
293///
294/// Total is where this differs from the language, and deliberately. `.code`
295/// source treats a non-Object operand as an *error* (`"abc".length` fails,
296/// which the README states as a rule), and `runtime.c` still has a
297/// `code_field` that does exactly that — for the compiler. It is not in
298/// `code_abi.h`, and this no longer calls it.
299///
300/// The reason is that a module cannot use a fallible accessor safely. Since
301/// phase 3 (2026-08-28) a runtime failure travels by a flag that only the
302/// *host's* generated code reads, and a `.so` carries its own copy of the
303/// runtime — so a failure raised inside a module sets the module's flag and
304/// is never seen. One function cannot be both fallible for the language and
305/// total for modules, so the ABI keeps the total one. A module that wants to
306/// refuse a wrong-typed operand says so itself, with [`exception`].
307pub fn field(out: &mut CodeValue, obj: &CodeValue, name: &str) {
308    match find_field(obj, name) {
309        Some(value) => copy(out, value),
310        None => null(out),
311    }
312}
313
314/// `arr[index]` element access, with the same totality as [`field`]: an
315/// out-of-bounds index, a non-Number index into an Array, a non-String key
316/// into an Object, or an `arr` that is neither, all write Null.
317///
318/// Matches `.code`'s own rules for everything except that last case, for the
319/// reason [`field`] gives.
320pub fn index(out: &mut CodeValue, arr: &CodeValue, i: &CodeValue) {
321    match arr.tag {
322        CodeTag::Array => {
323            // The language indexes arrays by Number, and only by a Number
324            // that is a whole one in range — `xs[1.5]` and `xs[99]` are both
325            // null, not errors.
326            let n = if i.tag == CodeTag::Number {
327                i.number
328            } else {
329                f64::NAN
330            };
331            let whole = n as i64;
332            if whole as f64 == n && whole >= 0 && whole < arr.len {
333                copy(out, unsafe { &*slot_at(arr.items, whole) });
334            } else {
335                null(out);
336            }
337        }
338        // An Object is keyed by Str — the same split `loop` uses — so a
339        // computed key is just `find_field` under another name.
340        CodeTag::Object => match read_str(i).and_then(|key| find_field(arr, key)) {
341            Some(value) => copy(out, value),
342            None => null(out),
343        },
344        _ => null(out),
345    }
346}
347
348/// Structural equality, matching `.code` source's `=` operator.
349pub fn values_equal(a: &CodeValue, b: &CodeValue) -> bool {
350    unsafe { code_values_equal(a, b) != 0 }
351}
352
353// `bool_value` and `assert_value` used to live here, wrapping
354// `code_bool_value` and `code_assert`. Both are gone as of phase 3
355// (2026-08-28) along with their declarations in `code_abi.h`: they are the
356// compiler's own — one checks an `and`/`or` operand, the other is the
357// `assert` statement — and since phase 3 they report trouble through a flag
358// that only the host's generated code reads, so a module calling one would
359// have had its failure silently swallowed. A module that cannot do its work
360// returns [`exception`] instead; it may never end the application.
361
362/// Raise a fatal module error, taking the whole host process down.
363///
364/// **Deprecated as of 2026-08-28, and not for modules to call.** A module
365/// may never end the application — see
366/// `docs/todo/errors-as-particles.md`. Report a failure by returning an
367/// [`exception`] instead, which the program receives as an ordinary value
368/// and may examine or ignore.
369///
370/// Kept only because `runtime.c` itself still uses it internally for
371/// conditions with no frame to return to (out of memory). It will leave
372/// this crate's API entirely once the C runtime has an error channel.
373#[deprecated(
374    since = "1.1.0",
375    note = "a module may not end the application; return `exception(out, source, message)` instead"
376)]
377pub fn runtime_error(message: &str) -> ! {
378    let c = cstr(message);
379    unsafe { code_runtime_error(c.as_ptr()) }
380}
381
382// ===========================================================================
383// Slot buffers — for Array/Object construction, which `runtime.c` expects
384// as a `CODE_VALUE_SLOT_SIZE`-strided scratch buffer of already-built
385// elements (see `code_array`/`code_object`'s doc comments in `runtime.c`;
386// `tests/native_modules/test_math`'s `factors`/`meta` exported vars are
387// the C-side version of the same pattern).
388// ===========================================================================
389
390/// A scratch buffer of `count` [`CodeValue`] slots, zero-initialized (so
391/// each slot starts in the same safe state [`CodeValue::zeroed`] documents).
392/// Build each element in place with [`SlotBuffer::slot_mut`], then hand the
393/// buffer to [`array`] or [`object`] — matching `runtime.c`'s "elements are
394/// retained and copied out of this buffer, never adopted by reference"
395/// contract, after which every slot you wrote must still be [`release`]d
396/// (the copy took its own reference; yours is still live until you drop it).
397pub struct SlotBuffer {
398    buf: Vec<u8>,
399    len: i64,
400}
401
402impl SlotBuffer {
403    pub fn new(count: usize) -> Self {
404        Self {
405            buf: vec![0u8; count * CODE_VALUE_SLOT_SIZE],
406            len: count as i64,
407        }
408    }
409
410    /// Slot `index` — write a value into it with [`number`]/[`owned_str`]/etc.
411    pub fn slot_mut(&mut self, index: i64) -> &mut CodeValue {
412        debug_assert!(index >= 0 && index < self.len);
413        unsafe { &mut *slot_at(self.buf.as_mut_ptr() as *mut c_void, index) }
414    }
415
416    fn as_items_ptr(&mut self) -> *mut c_void {
417        self.buf.as_mut_ptr() as *mut c_void
418    }
419
420    /// Release every slot. Call after handing the buffer to [`array`] or
421    /// [`object`] — they copy elements out, they don't take ownership of
422    /// this buffer's own references.
423    pub fn release_all(&mut self) {
424        for i in 0..self.len {
425            unsafe { code_release(slot_at(self.buf.as_mut_ptr() as *mut c_void, i)) }
426        }
427    }
428}
429
430/// Write an Array into `out`, copying (and retaining) `elems`'s slots.
431/// `elems` still owns its own references afterwards — release it once
432/// you're done (see [`SlotBuffer::release_all`]).
433pub fn array(out: &mut CodeValue, elems: &mut SlotBuffer) {
434    unsafe { code_array(out, elems.as_items_ptr(), elems.len) }
435}
436
437/// Write an Object into `out` from parallel `keys` and `values` (a
438/// [`SlotBuffer`] built the same way [`array`] expects). `code_object` copies
439/// both the key *pointers* and the key *bytes* into the value's own storage
440/// (since 2026-08-29), so the `&'static CStr` bound is stricter than the ABI
441/// needs — it is the ergonomic path for the common case of literal field
442/// names (`c"status"`, `c"body"`). For names built at runtime — a parsed
443/// JSON object, a database row — use [`object_dyn`].
444pub fn object(out: &mut CodeValue, keys: &[&'static CStr], values: &mut SlotBuffer) {
445    debug_assert_eq!(keys.len() as i64, values.len);
446    let mut key_ptrs: Vec<*const c_char> = keys.iter().map(|k| k.as_ptr()).collect();
447    unsafe {
448        code_object(
449            out,
450            key_ptrs.as_mut_ptr(),
451            values.as_items_ptr(),
452            values.len,
453        )
454    }
455}
456
457/// [`object`], for keys that are not `'static` — built from a parsed
458/// document, a database row, an HTTP form. `code_object` copies each key's
459/// bytes into the new value's own block, so the `&str`s only have to live
460/// for the duration of this call.
461pub fn object_dyn(out: &mut CodeValue, keys: &[&str], values: &mut SlotBuffer) {
462    debug_assert_eq!(keys.len() as i64, values.len);
463    let c_keys: Vec<std::ffi::CString> = keys.iter().map(|k| cstr(k)).collect();
464    let mut key_ptrs: Vec<*const c_char> = c_keys.iter().map(|k| k.as_ptr()).collect();
465    unsafe {
466        code_object(
467            out,
468            key_ptrs.as_mut_ptr(),
469            values.as_items_ptr(),
470            values.len,
471        )
472    }
473    // `c_keys` drops here: safe, `code_object` copied the bytes it needed.
474}
475
476// ===========================================================================
477// Reading helpers — for use inside `code_module_dispatch`.
478// ===========================================================================
479
480/// Read a field by name off an Object value. `None` if `v` isn't an
481/// Object or the field doesn't exist — mirrors `code_field`'s own
482/// permissive-null behavior, but as an `Option` instead of writing Null.
483pub fn find_field<'a>(v: &'a CodeValue, name: &str) -> Option<&'a CodeValue> {
484    if v.tag != CodeTag::Object || v.keys.is_null() {
485        return None;
486    }
487    for i in 0..v.len {
488        let key = unsafe { *v.keys.offset(i as isize) };
489        if key.is_null() {
490            continue;
491        }
492        let key_str = unsafe { CStr::from_ptr(key) };
493        if key_str.to_bytes() == name.as_bytes() {
494            return Some(unsafe { &*slot_at(v.items, i) });
495        }
496    }
497    None
498}
499
500/// Read `v` as a `&str`, if it's a Str with a valid UTF-8 payload.
501pub fn read_str(v: &CodeValue) -> Option<&str> {
502    if v.tag != CodeTag::Str || v.str.is_null() {
503        return None;
504    }
505    unsafe { CStr::from_ptr(v.str) }.to_str().ok()
506}
507
508/// Read `v` as an `f64`, if it's a Number.
509pub fn read_number(v: &CodeValue) -> Option<f64> {
510    (v.tag == CodeTag::Number).then_some(v.number)
511}
512
513/// Read `v` as a `bool`, if it's a Bool.
514pub fn read_bool(v: &CodeValue) -> Option<bool> {
515    (v.tag == CodeTag::Bool).then_some(v.boolean != 0)
516}
517
518/// Convenience: [`find_field`] + [`read_str`].
519pub fn read_field_str<'a>(v: &'a CodeValue, name: &str) -> Option<&'a str> {
520    read_str(find_field(v, name)?)
521}
522
523/// Convenience: [`find_field`] + [`read_number`].
524pub fn read_field_number(v: &CodeValue, name: &str) -> Option<f64> {
525    read_number(find_field(v, name)?)
526}
527
528/// Convenience: [`find_field`] + [`read_bool`].
529pub fn read_field_bool(v: &CodeValue, name: &str) -> Option<bool> {
530    read_bool(find_field(v, name)?)
531}
532
533/// Iterate an Array's elements.
534pub fn array_elems(v: &CodeValue) -> impl Iterator<Item = &CodeValue> {
535    let (items, len) = if v.tag == CodeTag::Array {
536        (v.items, v.len)
537    } else {
538        (std::ptr::null_mut(), 0)
539    };
540    (0..len).map(move |i| unsafe { &*slot_at(items, i) })
541}
542
543/// Iterate an Object's fields as `(key, value)` pairs, in stored order —
544/// the read counterpart to [`object`]/[`object_dyn`], for a handler that has
545/// to walk every field rather than name one ([`find_field`]). A field whose
546/// key is not valid UTF-8 is skipped. Not an Object: an empty iterator.
547pub fn object_entries(v: &CodeValue) -> impl Iterator<Item = (&str, &CodeValue)> {
548    let (keys, items, len) = if v.tag == CodeTag::Object && !v.keys.is_null() {
549        (v.keys, v.items, v.len)
550    } else {
551        (std::ptr::null_mut(), std::ptr::null_mut(), 0)
552    };
553    (0..len).filter_map(move |i| {
554        let key = unsafe { *keys.offset(i as isize) };
555        if key.is_null() {
556            return None;
557        }
558        let key = unsafe { CStr::from_ptr(key) }.to_str().ok()?;
559        Some((key, unsafe { &*slot_at(items, i) }))
560    })
561}
562
563/// Build a `{ _class = <class_name>, value = <fill's result> }` particle
564/// into `out` — the shape `emit ... to <alias> get x` expects a handler's
565/// result to have. Mirrors `runtime.c`'s own `code_make_result`, which a
566/// C module reaches via `#include "runtime.c"` but isn't exported for a
567/// separately-linked module to call directly, so this is a small
568/// reimplementation rather than an FFI binding.
569pub fn make_result(
570    out: &mut CodeValue,
571    class_name: &'static CStr,
572    fill: impl FnOnce(&mut CodeValue),
573) {
574    let mut value = CodeValue::zeroed();
575    fill(&mut value);
576    let mut buf = SlotBuffer::new(2);
577    borrowed_str(buf.slot_mut(0), class_name);
578    unsafe { code_copy(buf.slot_mut(1), &value) };
579    object(out, &[c"_class", c"value"], &mut buf);
580    buf.release_all();
581    release(&mut value);
582}
583
584// ===========================================================================
585// Inbound emissions — speaking first, rather than only answering.
586// ===========================================================================
587
588/// The host's pusher, handed over by `code_module_set_inbound`. `queue` is
589/// opaque — a module only ever passes it straight back. Mirrors
590/// `code_abi.h`'s `CodeEmitFn`.
591pub type CodeEmitFn = unsafe extern "C" fn(queue: *mut c_void, value: *const CodeValue);
592
593/// Where [`declare_inbound!`] parks what the host handed over. Two atomics
594/// rather than a `static mut`: the host sets these once at link time, and a
595/// module with a thread of its own would read them from that thread, so the
596/// access wants to be well-defined even though nothing does that yet.
597pub static INBOUND_QUEUE: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
598/// The `CodeEmitFn` as a raw address — `AtomicPtr` cannot hold a `fn`
599/// pointer directly, and this is only ever written by [`store_inbound`] and
600/// read back by [`emit_inbound`].
601pub static INBOUND_EMIT: AtomicUsize = AtomicUsize::new(0);
602
603/// Record what the host handed over. Called by the export
604/// [`declare_inbound!`] generates; not useful on its own.
605pub fn store_inbound(queue: *mut c_void, emit: CodeEmitFn) {
606    INBOUND_QUEUE.store(queue, Ordering::Release);
607    INBOUND_EMIT.store(emit as usize, Ordering::Release);
608}
609
610/// Generate the optional `code_module_set_inbound` export.
611///
612/// A macro rather than a plain function in this crate, and that is
613/// load-bearing: `#[no_mangle]` symbols defined in a dependency are not
614/// reliably kept in the final `cdylib`, so the export has to be emitted in
615/// *your* crate. One invocation at the top level is all it takes:
616///
617/// ```rust,ignore
618/// code_native::declare_inbound!();
619/// ```
620///
621/// A module that never speaks first simply doesn't invoke it — the export is
622/// optional, and the host checks for it rather than requiring it.
623#[macro_export]
624macro_rules! declare_inbound {
625    // A `.a` static module spells its own export name, because every `.a`
626    // linked into one program shares a flat symbol table and the host finds
627    // these by prefix (`nm`, see loader.rs's `static_module_symbols`). It is
628    // spelled out rather than pasted together from a prefix because
629    // `macro_rules!` cannot concatenate identifiers — and spelling it matches
630    // how a static module already writes its other three exports.
631    ($name:ident) => {
632        /// Handed the host's queue and pusher once, at link time.
633        ///
634        /// # Safety
635        ///
636        /// Called by the host with its own queue pointer and pusher; both
637        /// stay valid for as long as the module is loaded.
638        #[no_mangle]
639        pub unsafe extern "C" fn $name(queue: *mut ::std::ffi::c_void, emit: $crate::CodeEmitFn) {
640            $crate::store_inbound(queue, emit);
641        }
642    };
643    () => {
644        /// Handed the host's queue and pusher once, at link time.
645        ///
646        /// # Safety
647        ///
648        /// Called by the host with its own queue pointer and pusher; both
649        /// stay valid for as long as the module is loaded.
650        #[no_mangle]
651        pub unsafe extern "C" fn code_module_set_inbound(
652            queue: *mut ::std::ffi::c_void,
653            emit: $crate::CodeEmitFn,
654        ) {
655            $crate::store_inbound(queue, emit);
656        }
657    };
658}
659
660/// Generate the optional `code_module_inbound_reply` export, which is how a
661/// module hears what the program answered to something it pushed.
662///
663/// Takes the function to hand it to — `fn(particle: &CodeValue, result:
664/// &CodeValue)`. `result` is a `CODE_NULL` value when no handler matched;
665/// both references are the host's and are only valid for the duration of the
666/// call, so read what you need and copy it out.
667///
668/// ```rust,ignore
669/// fn answered(particle: &CodeValue, result: &CodeValue) { /* ... */ }
670/// code_native::declare_inbound_reply!(answered);
671/// ```
672///
673/// A macro rather than a plain function for the same reason as
674/// [`declare_inbound!`]: a `#[no_mangle]` symbol defined in a dependency is
675/// not reliably kept in the final `cdylib`.
676#[macro_export]
677macro_rules! declare_inbound_reply {
678    // A `.a` static module spells its own export name — see
679    // [`declare_inbound!`] for why.
680    ($name:ident, $handler:path) => {
681        /// Called by the host after a particle this module pushed was
682        /// dispatched.
683        ///
684        /// # Safety
685        ///
686        /// Both pointers are the host's and valid for this call only.
687        #[no_mangle]
688        pub unsafe extern "C" fn $name(
689            particle: *const $crate::CodeValue,
690            result: *const $crate::CodeValue,
691        ) {
692            if particle.is_null() || result.is_null() {
693                return;
694            }
695            $handler(&*particle, &*result);
696        }
697    };
698    ($handler:path) => {
699        /// Called by the host after a particle this module pushed was
700        /// dispatched.
701        ///
702        /// # Safety
703        ///
704        /// Both pointers are the host's and valid for this call only.
705        #[no_mangle]
706        pub unsafe extern "C" fn code_module_inbound_reply(
707            particle: *const $crate::CodeValue,
708            result: *const $crate::CodeValue,
709        ) {
710            if particle.is_null() || result.is_null() {
711                return;
712            }
713            $handler(&*particle, &*result);
714        }
715    };
716}
717
718/// Push a particle into the program, to be dispatched to *its* handlers the
719/// next time the host drains (between top-level statements).
720///
721/// Returns `false` when the host never called `code_module_set_inbound` —
722/// which happens whenever the module was loaded by something that does not
723/// support inbound emissions. Pushing is therefore always best-effort from
724/// the module's side, and a module must stay correct when nobody is
725/// listening.
726///
727/// The particle is deep-copied into the host's heap by the host's own
728/// pusher, so `value` may be released as soon as this returns.
729///
730/// **A pushed class the program has no handler for is a runtime error**, not
731/// a silent drop (`tests/fail_inbound_unhandled.code` pins that). Push only
732/// what the program has agreed to receive.
733pub fn emit_inbound(value: &CodeValue) -> bool {
734    let emit = INBOUND_EMIT.load(Ordering::Acquire);
735    if emit == 0 {
736        return false;
737    }
738    let queue = INBOUND_QUEUE.load(Ordering::Acquire);
739    // SAFETY: `emit` is non-zero only because `store_inbound` wrote a real
740    // `CodeEmitFn` there, and `queue` is whatever the host paired with it.
741    let emit: CodeEmitFn = unsafe { std::mem::transmute::<usize, CodeEmitFn>(emit) };
742    unsafe { emit(queue, value) };
743    true
744}
745
746// ===========================================================================
747// Failing without ending the program.
748// ===========================================================================
749
750/// Build `Exception { source, message, innerException }` into `out` — how a
751/// module reports that it could not do the work.
752///
753/// This is the *only* way a module should fail. A module may never end the
754/// application (`docs/todo/errors-as-particles.md`): the program receives
755/// this as an ordinary value through `get`, and may test it with
756/// `is Exception`, read `message`, or ignore it entirely.
757///
758/// `source` names the module, which a returned value cannot otherwise be
759/// asked — the caller knows what it emitted to, but an `Exception` stored,
760/// passed on, or wrapped as another's `innerException` has lost that.
761///
762/// `innerException` is null here; use [`exception_wrapping`] to carry the
763/// failure underneath this one.
764pub fn exception(out: &mut CodeValue, source: &str, message: &str) {
765    let mut inner = CodeValue::zeroed();
766    null(&mut inner);
767    exception_wrapping(out, source, message, &inner);
768    release(&mut inner);
769}
770
771/// [`exception`], carrying the failure that caused it as `innerException`.
772pub fn exception_wrapping(out: &mut CodeValue, source: &str, message: &str, inner: &CodeValue) {
773    let mut buf = SlotBuffer::new(4);
774    borrowed_str(buf.slot_mut(0), c"Exception");
775    owned_str(buf.slot_mut(1), source);
776    owned_str(buf.slot_mut(2), message);
777    copy(buf.slot_mut(3), inner);
778    object(
779        out,
780        &[c"_class", c"source", c"message", c"innerException"],
781        &mut buf,
782    );
783    buf.release_all();
784}
785
786/// Run a module's dispatch body so that a panic inside it becomes an
787/// [`exception`] rather than killing the host.
788///
789/// **Wrap every `code_module_dispatch` in this.** The guarantee it provides
790/// cannot be provided by the host: a panic escaping an `extern "C"` function
791/// aborts the process rather than unwinding, so the host's own
792/// `catch_unwind` never runs — the catch has to happen on this side of the
793/// FFI boundary, which is here.
794///
795/// What it covers is most of what "a badly written module" means in
796/// practice: `unwrap`/`expect` on `None` or `Err`, slice and index bounds,
797/// arithmetic overflow, explicit `panic!`/`assert!`, and panics raised
798/// inside dependencies. What it cannot cover is a deliberate `exit`, an
799/// infinite loop, or undefined behaviour reached through `unsafe`.
800///
801/// ```rust,ignore
802/// #[no_mangle]
803/// pub unsafe extern "C" fn code_module_dispatch(
804///     out: *mut CodeValue,
805///     particle: *const CodeValue,
806/// ) {
807///     guarded(&mut *out, "mymodule", |out| match read_field_str(&*particle, "_class") {
808///         Some("Double") => { /* ... */ }
809///         _ => null(out),
810///     })
811/// }
812/// ```
813pub fn guarded(out: &mut CodeValue, source: &str, body: impl FnOnce(&mut CodeValue)) {
814    let slot: *mut CodeValue = out;
815    // `AssertUnwindSafe` over the whole closure: `out` is a slot the host
816    // owns, there is no invariant of ours for a panic to leave half-broken,
817    // and whatever the body managed to write is released by the constructor
818    // `exception` runs next.
819    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
820        // SAFETY: `slot` came from the `&mut` above and outlives this call.
821        body(unsafe { &mut *slot })
822    }));
823    let Err(payload) = result else {
824        return;
825    };
826    // Rust's panic payload is a string for `panic!("...")` and `unwrap`
827    // alike; anything else is reported without a message rather than
828    // guessed at.
829    let detail = payload
830        .downcast_ref::<&str>()
831        .map(|s| (*s).to_string())
832        .or_else(|| payload.downcast_ref::<String>().cloned())
833        .unwrap_or_else(|| "panicked".to_string());
834    // SAFETY: as above — `catch_unwind` returning `Err` means the body
835    // stopped early, not that the slot went away.
836    exception(
837        unsafe { &mut *slot },
838        source,
839        &format!("module panicked: {detail}"),
840    );
841}
842
843#[cfg(test)]
844mod tests {
845    use super::*;
846
847    /// `{ a = 1, s = "hi" }`.
848    fn sample_object(out: &mut CodeValue) {
849        let mut values = SlotBuffer::new(2);
850        number(values.slot_mut(0), 1.0);
851        owned_str(values.slot_mut(1), "hi");
852        object(out, &[c"a", c"s"], &mut values);
853        values.release_all();
854    }
855
856    /// `[10, 20, 30]`.
857    fn sample_array(out: &mut CodeValue) {
858        let mut items = SlotBuffer::new(3);
859        for (i, v) in [10.0, 20.0, 30.0].into_iter().enumerate() {
860            number(items.slot_mut(i as i64), v);
861        }
862        array(out, &mut items);
863        items.release_all();
864    }
865
866    fn tag_of(f: impl FnOnce(&mut CodeValue)) -> CodeTag {
867        let mut out = CodeValue::zeroed();
868        f(&mut out);
869        let tag = out.tag;
870        release(&mut out);
871        tag
872    }
873
874    #[test]
875    fn object_dyn_builds_with_runtime_keys() {
876        // Keys owned by this scope, not `'static` — the case `object` can't
877        // take. `code_object` copies the bytes, so the built value outlives
878        // them.
879        let mut values = SlotBuffer::new(2);
880        number(values.slot_mut(0), 1.0);
881        number(values.slot_mut(1), 2.0);
882        let mut obj = CodeValue::zeroed();
883        {
884            let names: Vec<String> = vec!["one".into(), "two".into()];
885            let key_refs: Vec<&str> = names.iter().map(String::as_str).collect();
886            object_dyn(&mut obj, &key_refs, &mut values);
887            // `names`/`key_refs` drop at the end of this block — the built
888            // `obj` must not depend on them any more.
889        }
890        values.release_all();
891
892        assert_eq!(read_field_number(&obj, "one"), Some(1.0));
893        assert_eq!(read_field_number(&obj, "two"), Some(2.0));
894        release(&mut obj);
895    }
896
897    #[test]
898    fn object_entries_walks_every_field_in_order() {
899        let mut obj = CodeValue::zeroed();
900        sample_object(&mut obj); // { a = 1, s = "hi" }
901        let seen: Vec<(String, CodeTag)> = object_entries(&obj)
902            .map(|(k, v)| (k.to_owned(), v.tag))
903            .collect();
904        assert_eq!(
905            seen,
906            vec![
907                ("a".to_owned(), CodeTag::Number),
908                ("s".to_owned(), CodeTag::Str),
909            ]
910        );
911        release(&mut obj);
912
913        // Not an Object: empty, not a panic.
914        let mut n = CodeValue::zeroed();
915        number(&mut n, 5.0);
916        assert_eq!(object_entries(&n).count(), 0);
917        release(&mut n);
918    }
919
920    #[test]
921    fn field_reads_a_present_member() {
922        let mut obj = CodeValue::zeroed();
923        sample_object(&mut obj);
924        let mut out = CodeValue::zeroed();
925        field(&mut out, &obj, "s");
926        assert_eq!(read_str(&out), Some("hi"));
927        release(&mut out);
928        release(&mut obj);
929    }
930
931    /// The half `field` shares with the language: an absent member is null,
932    /// not a failure. The lookup was fine, it just found nothing.
933    #[test]
934    fn field_answers_null_for_an_absent_member() {
935        let mut obj = CodeValue::zeroed();
936        sample_object(&mut obj);
937        assert_eq!(tag_of(|out| field(out, &obj, "nope")), CodeTag::Null);
938        release(&mut obj);
939    }
940
941    /// The half it does *not* share, and the reason this is Rust rather than
942    /// a call into the ABI. `.code` source treats `"abc".length` as an error;
943    /// a module cannot use a fallible accessor, because a failure raised
944    /// inside a `.so` sets that copy's flag and nobody reads it. Total wins
945    /// here, and the module says so itself with `exception` if it minds.
946    #[test]
947    fn field_answers_null_on_a_non_object() {
948        let mut n = CodeValue::zeroed();
949        number(&mut n, 42.0);
950        assert_eq!(tag_of(|out| field(out, &n, "anything")), CodeTag::Null);
951        release(&mut n);
952    }
953
954    #[test]
955    fn index_reads_an_array_element() {
956        let mut arr = CodeValue::zeroed();
957        sample_array(&mut arr);
958        let mut i = CodeValue::zeroed();
959        number(&mut i, 1.0);
960        let mut out = CodeValue::zeroed();
961        index(&mut out, &arr, &i);
962        assert_eq!(read_number(&out), Some(20.0));
963        release(&mut out);
964        release(&mut arr);
965    }
966
967    /// Every way of missing an array element is null, matching the
968    /// language: out of range, negative, and a whole-number check that
969    /// rejects `1.5` rather than truncating it.
970    #[test]
971    fn index_answers_null_for_every_kind_of_miss() {
972        let mut arr = CodeValue::zeroed();
973        sample_array(&mut arr);
974        for probe in [99.0, -1.0, 1.5] {
975            let mut i = CodeValue::zeroed();
976            number(&mut i, probe);
977            assert_eq!(
978                tag_of(|out| index(out, &arr, &i)),
979                CodeTag::Null,
980                "index {probe} should be null"
981            );
982        }
983        // A non-Number index into an Array is null too, not an error.
984        let mut key = CodeValue::zeroed();
985        owned_str(&mut key, "0");
986        assert_eq!(tag_of(|out| index(out, &arr, &key)), CodeTag::Null);
987        release(&mut key);
988        release(&mut arr);
989    }
990
991    /// An Array is keyed by Number and an Object by Str — the same split
992    /// `loop` uses — so a computed key on an Object is `find_field` under
993    /// another name.
994    #[test]
995    fn index_reads_an_object_by_string_key() {
996        let mut obj = CodeValue::zeroed();
997        sample_object(&mut obj);
998        let mut key = CodeValue::zeroed();
999        owned_str(&mut key, "a");
1000        let mut out = CodeValue::zeroed();
1001        index(&mut out, &obj, &key);
1002        assert_eq!(read_number(&out), Some(1.0));
1003        release(&mut out);
1004        release(&mut key);
1005        release(&mut obj);
1006    }
1007
1008    #[test]
1009    fn index_answers_null_on_a_non_container() {
1010        let mut n = CodeValue::zeroed();
1011        number(&mut n, 42.0);
1012        let mut i = CodeValue::zeroed();
1013        number(&mut i, 0.0);
1014        assert_eq!(tag_of(|out| index(out, &n, &i)), CodeTag::Null);
1015        release(&mut n);
1016    }
1017}