Expand description
Safe(r) Rust bindings for writing a native .so module 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.
§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));
}
_ => code_runtime_error("unknown handler"),
}
}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.
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.
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.
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.
- assert_
value assert vsemantics: fatal error (never returns) ifvisn’ttrue.- bool_
value - Coerce
vto aboolthe way a boolean operator does, raising the same fatal error a type mismatch would in.codesource itself (opis the operator name, used only for that error message — e.g."&&"). - 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. - field
obj.fieldfield access, exactly like.codesource’s own semantics: writes Null intoouton a non-Object or missing field rather than erroring — seecode_field’s doc comment incode_abi.h.- 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. - index
arr[index]element access, exactly like.codesource’s own semantics: writes Null on a non-Array or out-of-bounds index.- 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 - Raise a fatal module error — mirrors
core’s own handlers. Never returns: likecore, this takes the whole host process down (code runincluded), the same tradeoff every native-extension mechanism makes. Seecode_abi.h’s doc comment. - 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). - values_
equal - Structural equality, matching
.codesource’s=operator.