Skip to content

Native Extensions

A native extension is a Rust library built against the resonon-ext crate. Writing and shipping one is covered in Ship a Native Extension.

All come from use resonon_ext::prelude::*;.

MacroOnMakes
#[ext_fn]a functiona function resonon can call
#[ext_class]a structa class whose instances resonon holds as opaque handles
#[ext_new]a function returning the classthe class’s constructor
#[ext_method(Class)]a function whose first parameter is &Class or &mut Classa method; &Class reads, &mut Class writes

ext_fn, ext_new and ext_method take an optional doc = "…", which resonon pkg inspect prints.

resonon_extension! {
name: "my_ext",
functions: [double, stepper_new, stepper_next],
classes: [Stepper]
}
FieldValue
namethe extension name that __ext dispatches on
functionsevery ext_fn, ext_new and ext_method
classesevery ext_class; may be left out when there are none

A class missing from classes leaks its instances: the handle cleanup the runtime calls is generated only for the classes listed there.

The Rust types a function or method can take and return, and the resonon value each one carries across the boundary:

Resonon valueExtValue variantRust parameterRust return
42, 3.14Number(f64)f64f64
A note, C4Note(f64)f64, the fractional MIDI pitch— (an f64 returns a Number)
true, falseBoolean(bool)boolbool
"hello"String&str (borrowed, read-only) or String (owned)String
#[1, 2, 3]ArrayVec<f64>, an array of numbersVec<f64>
NULNul—(), no return value
_ (rest)Rest——
An #[ext_class] instanceHandle(u64)——

Any other Rust type is a compile error that names the supported ones. &str cannot be returned: return String instead.

CallDoes
__ext(extension, function, ...args)calls function of the extension named extension, loading it on first use
reload_ext(extension)unloads the extension and loads its library again; fails while an object from it is alive
Rule
ThreadEvery call runs on the interpreter thread, never on the audio thread. The library is loaded on its first call.
PanicsFunctions generated by ext_fn, ext_new and ext_method catch a panic and return it as a resonon error carrying the panic message. An exported function written by hand, or in another language, must catch its own panics and return a non-zero code with an ExtValue::Error; a panic that unwinds across the boundary aborts the process.
HandlesA handle is valid only in the extension that made it. Passing it to another extension is an error.
LockingEach class has one lock, covering all its instances. A call into a class waits for any other call into it to finish.
Returned valuesStrings and arrays are copied across the boundary. Do not return a pointer to memory the extension keeps using.
AllocatorThe default Rust allocator. A custom #[global_allocator] is not supported.
TrustAn extension is not sandboxed: it runs with the process’s rights. resonon pkg inspect loads it.

The extension named my_ext is looked for in the bin/ directory of the package my-ext or my_ext: the current project if it is that package, then the packages under ./lib, then those under ./dependencies. In each, the platform build wins over the generic one:

  1. bin/<name>-<os>-<arch>.<ext>, e.g. my_ext-macos-aarch64.dylib, written by resonon build --dist
  2. bin/<name>.<ext>, e.g. my_ext.dylib, written by resonon build

<ext> is dylib, so or dll. - in a name is read as _.

A package’s module is bound under its directory name with - changed to _: use "my-ext"; binds my_ext. That name and the name in resonon_extension! are separate; the scaffold keeps them the same.