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