Attribute Macro export

Source
#[export]
Expand description

Exports a Rust function to OCaml.

This procedural macro handles the complexities of the OCaml Foreign Function Interface (FFI), allowing Rust functions to be called from OCaml code. It generates the necessary extern "C" wrapper function and manages type conversions and memory safety.

§Basic Usage

use ocaml_interop::{OCaml, OCamlRuntime, OCamlBytes, OCamlInt, ToOCaml};

#[ocaml_interop::export]
fn process_bytes(cr: &mut OCamlRuntime, data: OCaml<OCamlBytes>) -> OCaml<OCamlInt> {
    let byte_slice: &[u8] = &data.to_rust::<Vec<u8>>();
    let length = byte_slice.len() as i64;
    length.to_ocaml(cr)
}

The macro generates an extern "C" function with the same identifier as the Rust function (e.g., process_bytes in the example above).

§Key Features

  • Automatic FFI Boilerplate: Generates the extern "C" wrapper and handles argument/return value marshalling.
  • Type Safety: Utilizes types like OCaml<T> and BoxRoot<T> to provide safe abstractions over OCaml values.
  • Argument Handling:
    • The first argument must be &mut OCamlRuntime (or &OCamlRuntime if noalloc is used).
    • OCaml<'gc, T>: For OCaml values passed as arguments. These are not automatically rooted by the macro. Their lifetime 'gc is tied to the current function call’s OCamlRuntime scope. Root them explicitly (e.g., with BoxRoot<T>) if they need to persist beyond this scope or be re-passed to OCaml.
    • BoxRoot<T>: If an argument is declared as BoxRoot<T>, the macro automatically roots the incoming OCaml value before your function body executes. This ensures the value is valid throughout the function, even across further OCaml calls.
    • Direct Primitive Types: Supports direct mapping for Rust primitive types like f64, i64, i32, bool, and isize as arguments. The OCaml external declaration must use corresponding [@@unboxed] or [@untagged] attributes.
  • Return Types:
    • Typically, functions return OCaml<T>.
    • Direct primitive types (see above) can also be returned.
  • Panic Handling:
    • By default, Rust panics are caught and raised as an OCaml exception (RustPanic of string if registered, otherwise Failure).
    • This can be disabled with #[ocaml_interop::export(no_panic_catch)]. Use with caution.
  • Bytecode Function Generation:
    • Use #[ocaml_interop::export(bytecode = "my_ocaml_bytecode_function_name")] to generate a wrapper for OCaml bytecode compilation.
    • The OCaml external declaration should then specify both the bytecode and native function names: external rust_fn : int -> int = "bytecode_stub_name" "native_c_stub_name".
  • noalloc Attribute:
    • #[ocaml_interop::export(noalloc)] for functions that must not trigger OCaml GC allocations.
    • Requires the runtime argument to be cr: &OCamlRuntime (immutable).
    • Implies no_panic_catch. Panics in noalloc functions lead to undefined behavior.
    • The corresponding OCaml external must be annotated with [@@noalloc].
    • The user is responsible for ensuring no OCaml allocations occur in the Rust function body.

§Argument and Return Value Conventions

The macro handles the conversion between OCaml’s representation and Rust types.

§OCaml Values

  • OCaml<T>: Represents an OCaml value that is not yet rooted. Its lifetime is tied to the current OCaml runtime scope.
  • BoxRoot<T>: Represents an OCaml value that has been rooted and is protected from the OCaml garbage collector. The #[ocaml_interop::export] macro can automatically root arguments if they are specified as BoxRoot<T>.

§Direct Primitive Type Mapping

For performance, certain Rust primitive types can be directly mapped to unboxed or untagged OCaml types. This avoids boxing overhead.

Rust TypeOCaml TypeOCaml external Attribute(s) Needed
f64float[@@unboxed] (or on arg/ret type)
i64int64[@@unboxed] (or on arg/ret type)
i32int32[@@unboxed] (or on arg/ret type)
boolbool[@untagged] (or on arg/ret type)
isizeint[@untagged] (or on arg/ret type)
()unit(Usually implicit for return)

Example (OCaml external for direct primitives):

external process_primitive_values :
  (int [@untagged]) ->
  (bool [@untagged]) ->
  (float [@unboxed]) ->
  (int32 [@unboxed]) =
  "" "process_primitive_values"

For more detailed information, refer to the user guides, particularly Exporting Rust Functions (Part 3)