Expand description
Safe(r) Rust bindings for writing a native module — .so or .a — for
the Code programming language.
code_abi.h’s contract needs two things from a module: agreement on the
CodeValue wire layout, and a code_release (plus friends) built from
the real runtime.c rather than a reimplementation that merely looks
compatible — getting refcounting subtly wrong is the kind of bug that
corrupts memory rather than crashing where you’d notice. This crate’s
build.rs compiles the vendored runtime.c and links it into your
cdylib directly, so every function below calls the same code the host
runtime and every C module trust. (A .a module wants the opposite —
the host already has the one runtime there — which is what the
static-module feature turns off; see this crate’s README.)
§Quick start
use code_native::*;
#[no_mangle]
pub extern "C" fn code_module_abi_version() -> u32 {
CODE_ABI_VERSION
}
#[no_mangle]
pub unsafe extern "C" fn code_module_dispatch(out: *mut CodeValue, particle: *const CodeValue) {
let particle = &*particle;
match read_field_str(particle, "_class") {
Some("Double") => {
let value = read_field_number(particle, "value").unwrap_or(0.0);
make_result(&mut *out, "DoubleResult", |slot| code_number(slot, value * 2.0));
}
// A class this module does not handle answers null — see
// docs/todo/errors-as-particles.md.
_ => null(&mut *out),
}
}Build with crate-type = ["cdylib"], then link "libmymodule.so" as m
from .code source. See this crate’s README for the full walkthrough,
including .a static modules and code_module_vars.
To speak first rather than only answer — pushing particles into the
program, which is what Log/Exception/Tick-shaped traffic needs —
add declare_inbound! and call emit_inbound:
code_native::declare_inbound!();
fn report(message: &str) {
let mut p = CodeValue::zeroed();
// ... build a particle ...
emit_inbound(&p);
release(&mut p);
}A pushed class the program has no handler for is dropped, so a module may report without every program that links it having to listen.
code_module_dispatch and code_module_abi_version are the two required
exports — there is no macro generating them here (unlike the old
language’s code-native): the new ABI dropped the descriptor-table
design for one function a module dispatches through itself, so there is
no boilerplate left to generate. code_release needs no Rust code at
all — it comes from the linked runtime.c object automatically.
Macros§
- declare_
inbound - Generate the optional
code_module_set_inboundexport. - declare_
inbound_ reply - Generate the optional
code_module_inbound_replyexport, which is how a module hears what the program answered to something it pushed.
Structs§
- Code
Value - Code
VarList - Slot
Buffer - A scratch buffer of
countCodeValueslots, zero-initialized (so each slot starts in the same safe stateCodeValue::zeroeddocuments). Build each element in place withSlotBuffer::slot_mut, then hand the buffer to [array] orobject— matchingruntime.c’s “elements are retained and copied out of this buffer, never adopted by reference” contract, after which every slot you wrote must still bereleased (the copy took its own reference; yours is still live until you drop it).
Enums§
Constants§
- CODE_
ABI_ VERSION - Current ABI version. A module’s
code_module_abi_versionmust return this. - CODE_
VALUE_ SLOT_ SIZE - Byte stride of an array/object element buffer — not
size_of::<CodeValue>(). This is a frozen ABI constant with headroom forCodeValueto grow without breaking already-compiled modules; always address a buffer throughslot_at, never by casting to*mut CodeValueand indexing.
Statics§
- INBOUND_
EMIT - The
CodeEmitFnas a raw address —AtomicPtrcannot hold afnpointer directly, and this is only ever written bystore_inboundand read back byemit_inbound. - INBOUND_
QUEUE - Where
declare_inbound!parks what the host handed over. Two atomics rather than astatic mut: the host sets these once at link time, and a module with a thread of its own would read them from that thread, so the access wants to be well-defined even though nothing does that yet.
Functions§
- array
- Write an Array into
out, copying (and retaining)elems’s slots.elemsstill owns its own references afterwards — release it once you’re done (seeSlotBuffer::release_all). - array_
elems - Iterate an Array’s elements.
- boolean
- Write a Bool into
out. - borrowed_
str - Write a Str into
out, borrowingsfor'static(a string literal or otherwise permanently-alive buffer) rather than copying it — matchingcode_str’s own borrowing contract. Useowned_strfor a value built at runtime that needs its own heap block. - code_
release ⚠ - The ABI’s required
code_releaseexport. Defined here, as a real Rust function, rather than left as whateverruntime.c’s owncode_releasewould otherwise be:cdylibtargets get--exclude-libs=ALLfrom rustc by default, which hides every symbol pulled in from a linked static archive (exactly whatbuild.rs’scc::Build::compileproduces fromruntime.c) out of the shared library’s dynamic symbol table — even though this crate’s own code calls it just fine internally. A symbol the crate defines directly (this function) isn’t subject to that exclusion, so renaming the archive’s copy and re-exporting it from here is what makes the host’sdlsym("code_release")actually find it. - copy
- Deep-copy
srcintoout—outends up owning its own references to everythingsrcpoints at, andsrcis left untouched. This is how a handler passes a value it did not build itself along (e.g. anEchoreturning its operand): the copy takes new references, so neither side’s lifetime constrains the other. - emit_
inbound - Push a particle into the program, to be dispatched to its handlers the next time the host drains (between top-level statements).
- exception
- Build
Exception { source, message, innerException }intoout— how a module reports that it could not do the work. - exception_
wrapping exception, carrying the failure that caused it asinnerException.- field
obj.fieldfield access. Total: a missing field, or anobjthat is not an Object at all, writes Null intoout.- find_
field - Read a field by name off an Object value.
Noneifvisn’t an Object or the field doesn’t exist — mirrorscode_field’s own permissive-null behavior, but as anOptioninstead of writing Null. - guarded
- Run a module’s dispatch body so that a panic inside it becomes an
exceptionrather than killing the host. - index
arr[index]element access, with the same totality asfield: an out-of-bounds index, a non-Number index into an Array, a non-String key into an Object, or anarrthat is neither, all write Null.- make_
result - Build a
{ _class = <class_name>, value = <fill's result> }particle intoout— the shapeemit ... to <alias> get xexpects a handler’s result to have. Mirrorsruntime.c’s owncode_make_result, which a C module reaches via#include "runtime.c"but isn’t exported for a separately-linked module to call directly, so this is a small reimplementation rather than an FFI binding. - null
- Write Null into
out. - number
- Write a Number into
out. - object
- Write an Object into
outfrom parallelkeysandvalues(aSlotBufferbuilt the same way [array] expects).keysmust outlive nothing in particular —code_objectcopies the pointers, and C-string field names are expected to be'static(string literals), matchingcode_abi.h’s own “key pointers are read-only data” note. - owned_
str - Write a Str into
outfrom a freshly-built Rust string. Leaks theCString— acceptable here because the value crosses into the host’s own heap the moment yourcode_module_dispatchreturns (the host deep-copies your result and then calls your module’scode_releaseon it, which only ever frees whatruntime.c’s own allocator built, never this leaked buffer). - read_
bool - Read
vas abool, if it’s a Bool. - read_
field_ bool - Convenience:
find_field+read_bool. - read_
field_ number - Convenience:
find_field+read_number. - read_
field_ str - Convenience:
find_field+read_str. - read_
number - Read
vas anf64, if it’s a Number. - read_
str - Read
vas a&str, if it’s a Str with a valid UTF-8 payload. - release
- Release whatever
vholds — call on every temporaryCodeValueyou built and no longer need (matchingruntime.c’s own refcounting rule: every slot that ever named a heap block owns exactly one reference to it). - retain
- Increment
v’s refcount — needed only if you’re holding onto aCodeValueyou didn’t just build yourself (e.g. a borrowed field fromfind_field) somewhere that will outlive the call it came from. Every retained value must be balanced by arelease. - runtime_
error Deprecated - Raise a fatal module error, taking the whole host process down.
- slot_at
- Addresses slot
indexof aCODE_VALUE_SLOT_SIZE-strided buffer — the Rust equivalent ofcode_abi.h’scode_slot_at. Pure pointer arithmetic, safe to reimplement independently (no allocator/refcount logic to drift fromruntime.c). - store_
inbound - Record what the host handed over. Called by the export
declare_inbound!generates; not useful on its own. - values_
equal - Structural equality, matching
.codesource’s=operator.
Type Aliases§
- Code
Emit Fn - The host’s pusher, handed over by
code_module_set_inbound.queueis opaque — a module only ever passes it straight back. Mirrorscode_abi.h’sCodeEmitFn.