Open Manual

Native boundary

Native boundary rules cover layouts, extern blocks, pointers, handles, strings,

ownership, and ABI behavior.

Layouts

layout Name {
   Type field,
   Type field2
}

Layout field order is part of the ABI. A layout describes memory shape at a

native boundary. Reordering fields changes the ABI.

Extern blocks

extern "library" {
   fn symbol(Type arg) Type
}

extern {
   fn process_symbol(Type arg) Type
}

An extern block declares native symbols. The library string selects the linked

or loaded library. A bare extern block names symbols already available from the

current process; extern "" is accepted as the older spelling.

Header imports

#include imports declarations from C headers through the Clang-backed FFI

path:

#include <stdlib.h> as "c"
#include "./ffi.h" as ""

Use a namespace alias for broad system headers when you need the C spelling

without risking Nytrix-name collisions. Unprefixed imports (as "") expose

constants, macros, enum values, and non-conflicting functions directly; if a C

function name collides with an existing Nytrix symbol, that function import is

skipped so wrappers such as atoi and atof remain usable. Import the header

with an alias when you need the skipped C function.

Header imports follow transitive includes. Object-like integer macros,

shift/bitwise macro expressions, enum constants, typedef structs, and

pointer-bearing structs become visible when libclang can resolve them.

Imported typedef structs are available as layout constructors. Use &value

when a C function expects an out pointer:

#include <sys/time.h> as ""

mut timeval tv = timeval(0, 0)
gettimeofday(&tv, NULL)

NULL is accepted as the C null pointer spelling and lowers to nil/0.

For production native calls, prefer extern, #include, and layout. The

dynamic std.os.ffi helpers are useful at the REPL or for exploratory probes,

but they trade compile-time ABI knowledge for runtime descriptors and capped

dynamic dispatch.

Pointers

*T is an addressable pointer to T. Pointer values model memory addresses.

They require correct lifetime, alignment, and element type.

Handles

handle is an opaque native scalar. A handle is not a pointer unless the API

documents that conversion. Handle cleanup uses the close, destroy, or release

function documented by the owning API.

Layout helpers

Runtime layout helpers expose ABI metadata and field access:

__layout_size("Name")
__layout_align("Name")
__layout_offset("Name", "field")
store_layout(ptr, "Name", values...)
load_layout(ptr, "Name", "field")

Typed raw loads and stores include integer, float, bool, pointer, and handle

forms such as load8, load16, load32, load32_f32, load64_f64,

load64_h, store8, store32, and store_layout.

Raw memory helpers use byte offsets. The public wrappers default the offset to

zero:

HelperShapeUse
load8(p, i=0) / store8(p, v, i=0)byteRaw bytes.
load16, load32, load64tagged int/value loadNytrix scalar slots and raw integer data.
load64_i(p, i=0) / store64_i(p, v, i=0)int viewTyped integer reads and writes.
load64_h(p, i=0) / store64_h(p, v, i=0)handle viewPointer or handle-sized native values.
load32_f32, load64_f64float viewNative float fields.
store32_f32, store64_f64float storeNative float fields.

The runtime intrinsics behind these wrappers use (p, offset, value) store

order. User code should call the std.core wrappers above.

std.os.ffi.CStruct is a dynamic descriptor form. It is intentionally flexible

and slower than layout; compiled layout access resolves offsets directly.

Inline assembly and intrinsics

asm("mov $1, $0", "=r,r", value)
llvm("ctpop.i64", value)
llvm("llvm.cttz.i64", value, false)

asm lowers inline assembly for the active backend and target architecture.

@naked functions can contain complete target-specific assembly bodies. The

llvm builtin calls LLVM intrinsics; the llvm. prefix is optional for

intrinsic names.

Strings and bytes

FFI text handling is a boundary. APIs document whether strings are:

These forms are not interchangeable.

Ownership

Ownership attributes and API docs define who allocates and who frees native

values. A wrapper can provide scoped cleanup, but raw native values require

explicit ownership handling.

When a native call returns owned memory or a handle, model that in the wrapper

with ownership contracts and cleanup:

@returns_owned
fn make_buffer(){ malloc(64) }

@consumes(p)
@releases(p)
fn free_buffer(p) int {
   free(p)
   0
}

Related