Functions
This page covers free functions - standalone functions that aren’t attached to a struct or class. For methods on objects, see Classes.
Mark a Rust function with #[export] and BoltFFI generates a corresponding function in each target language. The function signature, parameter types, and return type all map according to the rules in Types.
Function names may be renamed to match target language conventions. For example, get_user becomes getUser in languages that use camelCase and GetUser in C#.
Basic export
The simplest case: a function that takes primitives and returns a primitive.
Parameters
Primitives and strings
Primitive parameters pass directly with no overhead. Strings require copying since each language manages its own memory.
Use &str for string parameters (you’re borrowing) and String for return values (you’re transferring ownership).
Structs and enums
Functions can accept structs and enums marked with #[data]. The data and all its fields move across the boundary.
Slices
Use &[T] to accept a collection without taking ownership. The caller’s array or list is accessible as a slice inside Rust.
Optional
Use Option<T> when a parameter might not be provided. The caller passes nil/null or a value.
Classes
Functions can accept class instances as parameters. The class reference passes across the boundary without copying the object.
Callback traits
Functions can accept callback traits. A callback trait is a Rust trait marked with #[export] that the target language implements. Unlike closures (single anonymous function), callback traits can have multiple methods and carry state. See Callbacks for more.
Return types
Option
Return Option<T> when a value might not exist. The caller gets a nullable type they can check before using.
Result
Return Result<T, E> when an operation can fail. The error type must be marked with #[error]. The generated function throws in the target language.
Use Option when absence is expected. Use Result when absence is an error.
Vec
Return Vec<T> when you’re producing a collection. Each element moves across the boundary.
Async functions
Mark a function async and BoltFFI generates an async function in the target language:
- Swift:
async - Kotlin:
suspend - Java:
CompletableFutureon Java 8+, with virtual-thread blocking calls on Java 21+ - C#:
Task - TypeScript:
Promise
BoltFFI has no built-in executor. You choose your Rust async runtime (Tokio, async-std, smol, etc.), and BoltFFI bridges the async boundary between Rust and the target language. See Async for more.
Closures
Functions can accept closures as parameters. The closure is called synchronously within the Rust function.
Each closure call crosses the FFI boundary. If you’re calling the closure many times in a tight loop, consider restructuring to reduce crossings.
Limitations
-
Generic functions like
fn max<T: Ord>(a: T, b: T) -> Tare not supported. Create concrete versions for each type you need. -
Functions cannot return references. Return owned data instead.
-
Closures that outlive the function call (stored for later use) are not supported. The closure must be called within the function body.
// Not supported - generic
#[export]
pub fn max<T: Ord>(a: T, b: T) -> T { ... }
// Supported - concrete versions
#[export]
pub fn max_i32(a: i32, b: i32) -> i32 {
if a > b { a } else { b }
}
#[export]
pub fn max_f64(a: f64, b: f64) -> f64 {
if a > b { a } else { b }
}