Skip to main content

Crate code_native

Crate code_native 

Source
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_inbound export.
declare_inbound_reply
Generate the optional code_module_inbound_reply export, which is how a module hears what the program answered to something it pushed.

Structs§

CodeValue
CodeVarList
SlotBuffer
A scratch buffer of count CodeValue slots, zero-initialized (so each slot starts in the same safe state CodeValue::zeroed documents). Build each element in place with SlotBuffer::slot_mut, then hand the buffer to [array] or object — matching runtime.c’s “elements are retained and copied out of this buffer, never adopted by reference” contract, after which every slot you wrote must still be released (the copy took its own reference; yours is still live until you drop it).

Enums§

CodeTag

Constants§

CODE_ABI_VERSION
Current ABI version. A module’s code_module_abi_version must 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 for CodeValue to grow without breaking already-compiled modules; always address a buffer through slot_at, never by casting to *mut CodeValue and indexing.

Statics§

INBOUND_EMIT
The CodeEmitFn as a raw address — AtomicPtr cannot hold a fn pointer directly, and this is only ever written by store_inbound and read back by emit_inbound.
INBOUND_QUEUE
Where declare_inbound! parks what the host handed over. Two atomics rather than a static 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. elems still owns its own references afterwards — release it once you’re done (see SlotBuffer::release_all).
array_elems
Iterate an Array’s elements.
boolean
Write a Bool into out.
borrowed_str
Write a Str into out, borrowing s for 'static (a string literal or otherwise permanently-alive buffer) rather than copying it — matching code_str’s own borrowing contract. Use owned_str for a value built at runtime that needs its own heap block.
code_release
The ABI’s required code_release export. Defined here, as a real Rust function, rather than left as whatever runtime.c’s own code_release would otherwise be: cdylib targets get --exclude-libs=ALL from rustc by default, which hides every symbol pulled in from a linked static archive (exactly what build.rs’s cc::Build::compile produces from runtime.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’s dlsym("code_release") actually find it.
copy
Deep-copy src into outout ends up owning its own references to everything src points at, and src is left untouched. This is how a handler passes a value it did not build itself along (e.g. an Echo returning 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 } into out — how a module reports that it could not do the work.
exception_wrapping
exception, carrying the failure that caused it as innerException.
field
obj.field field access. Total: a missing field, or an obj that is not an Object at all, writes Null into out.
find_field
Read a field by name off an Object value. None if v isn’t an Object or the field doesn’t exist — mirrors code_field’s own permissive-null behavior, but as an Option instead of writing Null.
guarded
Run a module’s dispatch body so that a panic inside it becomes an exception rather than killing the host.
index
arr[index] element access, with the same totality as field: an out-of-bounds index, a non-Number index into an Array, a non-String key into an Object, or an arr that is neither, all write Null.
make_result
Build a { _class = <class_name>, value = <fill's result> } particle into out — the shape emit ... to <alias> get x expects a handler’s result to have. Mirrors runtime.c’s own code_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 out from parallel keys and values (a SlotBuffer built the same way [array] expects). keys must outlive nothing in particular — code_object copies the pointers, and C-string field names are expected to be 'static (string literals), matching code_abi.h’s own “key pointers are read-only data” note.
owned_str
Write a Str into out from a freshly-built Rust string. Leaks the CString — acceptable here because the value crosses into the host’s own heap the moment your code_module_dispatch returns (the host deep-copies your result and then calls your module’s code_release on it, which only ever frees what runtime.c’s own allocator built, never this leaked buffer).
read_bool
Read v as a bool, 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 v as an f64, if it’s a Number.
read_str
Read v as a &str, if it’s a Str with a valid UTF-8 payload.
release
Release whatever v holds — call on every temporary CodeValue you built and no longer need (matching runtime.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 a CodeValue you didn’t just build yourself (e.g. a borrowed field from find_field) somewhere that will outlive the call it came from. Every retained value must be balanced by a release.
runtime_errorDeprecated
Raise a fatal module error, taking the whole host process down.
slot_at
Addresses slot index of a CODE_VALUE_SLOT_SIZE-strided buffer — the Rust equivalent of code_abi.h’s code_slot_at. Pure pointer arithmetic, safe to reimplement independently (no allocator/refcount logic to drift from runtime.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 .code source’s = operator.

Type Aliases§

CodeEmitFn
The host’s pusher, handed over by code_module_set_inbound. queue is opaque — a module only ever passes it straight back. Mirrors code_abi.h’s CodeEmitFn.