# BoltFFI Documentation

> Complete Markdown documentation for BoltFFI.

Index: https://boltffi.dev/llms.txt

Source: https://boltffi.dev/docs/overview

# BoltFFI

BoltFFI is a tool that generates foreign-language bindings from Rust libraries. It fits the practice of consolidating business logic in a single Rust library while targeting multiple platforms, making it simpler to develop and maintain a cross-platform codebase. `boltffi pack` builds native artifacts and arranges the platform package, including an XCFramework for Apple, jniLibs for Android, a JNI-backed JAR for Java, a NuGet package for .NET, an npm package for WASM/TypeScript, and Python wheels. The generated wrappers handle type conversion, memory management, and error propagation across the language boundary.

The generated bindings are native code that uses each language's idioms. Swift bindings use structured concurrency with async/await. Kotlin bindings use coroutines. Java bindings use CompletableFuture and functional interfaces. C# bindings use `Task`, `IDisposable`, and `IAsyncEnumerable`. TypeScript bindings use Promises and integrate with WASM. Python bindings use typed package modules, asyncio, and wheels that import like normal Python packages. Errors become native exceptions. Types map to their platform equivalents. The result is code you can use natively, without dealing with pointers or manual memory management.

BoltFFI uses a zero-copy approach where possible. Primitives and structs containing only primitives pass across the boundary without serialization. Complex types like strings and collections use a wire format.

<img src="/architecture.svg" alt="BoltFFI architecture diagram" class="my-8 mx-auto max-w-3xl" />

**Rust**

```rust
use boltffi::*;

#[data]
pub struct Point {
  pub lat: f64,
  pub lng: f64,
}

#[export]
pub fn distance(from: Point, to: Point) -> f64 {
  let dlat = to.lat - from.lat;
  let dlng = to.lng - from.lng;
  (dlat * dlat + dlng * dlng).sqrt()
}
```

**Swift**

```swift
// Generated Swift
public struct Point {
  public var lat: Double
  public var lng: Double
}

func distance(from: Point, to: Point) -> Double

// Usage
let d = distance(from: a, to: b)
```

**Kotlin**

```kotlin
// Generated Kotlin
data class Point(
  val lat: Double,
  val lng: Double
)

fun distance(from: Point, to: Point): Double

// Usage
val d = distance(a, b)
```

**Java**

```java
// Java 16+ (records)
public record Point(double lat, double lng) {}

// Java 8+ (classes)
public final class Point {
  public final double lat;
  public final double lng;
}

static double distance(Point from, Point to)

// Usage
double d = distance(a, b);
```

**C#**

```csharp
// Generated C#
public readonly record struct Point(
  double Lat,
  double Lng
);

public static double Distance(Point from, Point to)

// Usage
double d = MyLib.Distance(a, b);
```

**TypeScript**

```typescript
// Generated TypeScript
interface Point {
  lat: number;
  lng: number;
}

function distance(from: Point, to: Point): number

// Usage
const d = distance(a, b)
```

**Python**

```python
# Generated Python
from dataclasses import dataclass

@dataclass
class Point:
  lat: float
  lng: float

def distance(from_: Point, to: Point) -> float: ...

# Usage
d = distance(a, b)
```

## How it works

1. Add `boltffi` as a dependency
2. Mark types with `#[data]` and functions with `#[export]`
3. Run `boltffi pack apple`, `boltffi pack android`, `boltffi pack java`, `boltffi pack csharp`, `boltffi pack wasm`, or `boltffi pack python`. `pack all` builds every enabled target.
4. Import or include the generated bindings in your project

The generated bindings use native idioms. Swift gets `async`/`await` and throwing functions. Kotlin gets coroutines and sealed classes. Java gets CompletableFuture and records (Java 16+) or plain classes (Java 8+). C# gets `Task`, records/structs, `IDisposable` wrappers, and `await foreach` streams. TypeScript gets Promises and typed interfaces. Python gets typed package modules, asyncio functions, and wheels. Your Rust library feels like a native SDK.

## Performance

Primitives pass as raw values. Structs with primitive fields pass as pointers. Only strings and nested collections go through encoding.

Benchmarks on Apple Silicon, compared to UniFFI (Swift/Kotlin):

| Operation            | BoltFFI  | UniFFI       | Speedup |
| -------------------- | -------- | ------------ | ------- |
| Primitive (i32, f64) | \<1 ns   | 625 ns       | ∞       |
| Small string         | 42 ns    | 1,958 ns     | 47x     |
| 1,000 structs        | 1,958 ns | 1,195,354 ns | 611x    |
| 10,000 i32 values    | 1,291 ns | 1,991,146 ns | 1,542x  |

Compared to wasm-bindgen (TypeScript):

| Operation                 | BoltFFI   | wasm-bindgen  | Speedup |
| ------------------------- | --------- | ------------- | ------- |
| Primitive (i32, f64)      | 2 ns      | 2 ns          | tie     |
| 1k string                 | 806 ns    | 2,921 ns      | 3.6x    |
| 1,000 structs (6 fields)  | 21,931 ns | 4,037,879 ns  | 184x    |
| 1,000 structs (10 fields) | 29,886 ns | 13,532,530 ns | 453x    |

Full benchmark code: [bench\_demo](https://github.com/boltffi/boltffi/tree/main/bench_demo).

## Supported languages

BoltFFI has full support for Swift, Kotlin, Java, C#, TypeScript (WASM), and Python. Adding support for more languages is in progress.

| Language          | Status      |
| ----------------- | ----------- |
| Swift             | Supported   |
| Kotlin            | Supported   |
| Java              | Supported   |
| C#                | Supported   |
| TypeScript (WASM) | Supported   |
| C                 | Partial     |
| Python            | Supported   |
| C++               | Planned     |
| Ruby              | Planned     |
| Dart              | In progress |
| Scala             | Planned     |
| Go                | Planned     |
| Lua               | Potential   |
| R                 | Potential   |

If you want a language added or considered, [open an issue](https://github.com/boltffi/boltffi/issues).

## What you can export

- Primitives, strings, structs, enums, Option, Result, Vec, HashMap
- Sync and async functions
- Closures and callback traits
- Classes with methods
- Global and associated constants
- Async streams
- Rust errors as exceptions

See [Types](/docs/types.md) for the full mapping, or [Getting Started](/docs/getting-started.md) to set up a project.

---

Source: https://boltffi.dev/docs/getting-started

# Getting Started

This guide walks you through creating a Rust library and packaging it for use in other languages.

## Prerequisites

- Rust 1.70+ ([rustup.rs](https://rustup.rs))
- For Apple: Xcode 15+ with command line tools
- For Android: Android Studio with NDK
- For Java: JDK 8+ with `javac` and a C compiler for the JNI bridge
- For C#: .NET SDK 10.0+ for the current C# demo and generated binding tests
- For WASM: Node.js 18+ and wasm-pack (`cargo install wasm-pack`)
- For Python, Python 3.10+ with pip

## Install BoltFFI

```bash
cargo install boltffi_cli
```

## Create a Rust library

```bash
cargo new --lib mylib
cd mylib
```

Edit `Cargo.toml`:

```toml
[package]
name = "mylib"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["staticlib", "cdylib"]
```

Install BoltFFI:

```bash
cargo add boltffi
```

## Write your code

Two attributes do most of the work:

- `#[data]` marks a struct or enum as a record (value type, copied across the boundary)
- `#[export]` exposes a function or impl block to the target language

Replace `src/lib.rs`:

```rust
use boltffi::*;

#[data]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

#[export]
pub fn distance(a: Point, b: Point) -> f64 {
    ((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt()
}
```

## Build, package, or generate

```bash
boltffi init              # creates boltffi.toml
boltffi pack all --release    # packages all enabled packaged targets
```

This creates:

- `dist/apple/` - XCFramework + Swift bindings
- `dist/android/` - JNI libraries + Kotlin bindings
- `dist/java/` - JNI library + Java bindings
- `dist/csharp/` - NuGet package + native runtime assets
- `dist/wasm/` - WASM module + TypeScript bindings
- `dist/python/` - Python source package + wheels

To build for a single platform:

```bash
boltffi pack apple 
boltffi pack android
boltffi pack java
boltffi pack csharp
boltffi pack wasm
boltffi pack python
```

## Use in Swift

Add the package to Xcode (File → Add Package Dependencies → local path to `dist/apple`), then:

```swift
import MyLib

let p1 = Point(x: 0, y: 0)
let p2 = Point(x: 3, y: 4)
print(distance(a: p1, b: p2))  // 5.0
```

## Use in Kotlin

Copy `dist/android` into your Android project, then:

```kotlin
import com.example.mylib.*

val p1 = Point(0.0, 0.0)
val p2 = Point(3.0, 4.0)
println(distance(p1, p2))  // 5.0
```

## Use in Java

Add the generated sources and JNI library to your project, then:

```java
import com.example.mylib.*;

Point p1 = new Point(0.0, 0.0);
Point p2 = new Point(3.0, 4.0);
System.out.println(distance(p1, p2));  // 5.0
```

On Java 16+ with `min_version = 16` in `boltffi.toml`, records are used instead of classes. On Java 8+, the same API works with generated final classes.

## Use in C\#

Build the NuGet package and add it to your .NET project from a local package source:

```bash
boltffi pack csharp
```

This produces a `.nupkg` under `dist/csharp/packages/` with the bindings and the native runtime
assets bundled under `runtimes/<rid>/native/`. Reference it from your project just like any other
NuGet dependency, then:

```csharp
using MyLib;

var p1 = new Point(0.0, 0.0);
var p2 = new Point(3.0, 4.0);
Console.WriteLine(MyLib.Distance(p1, p2));  // 5.0
```

## Use in TypeScript

The WASM package works in bundlers, Node.js, and browsers.

### With a Bundler (Vite, webpack)

Import directly - your bundler handles WASM loading:

```typescript
import { Point, distance } from 'mylib';

const p1: Point = { x: 0, y: 0 };
const p2: Point = { x: 3, y: 4 };
console.log(distance(p1, p2));  // 5.0
```

### In Node.js

Await initialization before calling functions:

```typescript
import { initialized, Point, distance } from 'mylib';

await initialized;

const p1: Point = { x: 0, y: 0 };
const p2: Point = { x: 3, y: 4 };
console.log(distance(p1, p2));  // 5.0
```

### In a Browser (no bundler)

Use the web entrypoint and call `init()`:

```html
<script type="module">
  import init, { distance } from './pkg/web.js';
  
  await init();
  
  const p1 = { x: 0, y: 0 };
  const p2 = { x: 3, y: 4 };
  console.log(distance(p1, p2));  // 5.0
</script>
```

## Use in Python

Install the wheel from `dist/python/wheelhouse`, then import the generated package.

```bash
python -m pip install dist/python/wheelhouse/mylib-0.1.0-*.whl
```

```python
import mylib

p1 = mylib.Point(0, 0)
p2 = mylib.Point(3, 4)
print(mylib.distance(p1, p2))  # 5.0
```

## What you learned

- `#[data]` creates records (value types)
- `#[export]` exposes functions
- `boltffi pack` handles build, codegen, and packaging in one step

## Next steps

- [Types](/docs/types.md) - how Rust types map to target languages
- [Records](/docs/records.md) - structs, enums, default values
- [Classes](/docs/classes.md) - stateful objects with methods
- [Functions](/docs/functions.md) - parameters, return types, errors
- [Async](/docs/async.md) - async functions and methods

---

Source: https://boltffi.dev/docs/installation

# Installation

## Prerequisites

- **Rust 1.70+** - Install from [rustup.rs](https://rustup.rs)
- **For iOS**: Xcode 15+ with command line tools
- **For Android**: Android Studio with NDK installed
- **For Java**: JDK 8+ with `javac` and a C compiler for the JNI bridge
- **For C#**: .NET SDK 10.0+ for the current C# demo and generated binding tests
- **For Python**: Python 3.10+ with pip

## Install the CLI

```bash
cargo install boltffi_cli
```

### Download a prebuilt CLI

Each [GitHub release](https://github.com/boltffi/boltffi/releases) includes a CLI archive and a matching SHA-256 checksum file:

| Platform             | Archive                             | Checksum                                   |
| -------------------- | ----------------------------------- | ------------------------------------------ |
| Linux x86\_64        | `boltffi-linux-x86_64.tar.gz`       | `boltffi-linux-x86_64.tar.gz.sha256`       |
| Linux x86\_64 (musl) | `boltffi-linux-x86_64-musl.tar.gz`  | `boltffi-linux-x86_64-musl.tar.gz.sha256`  |
| Linux ARM64          | `boltffi-linux-aarch64.tar.gz`      | `boltffi-linux-aarch64.tar.gz.sha256`      |
| Linux ARM64 (musl)   | `boltffi-linux-aarch64-musl.tar.gz` | `boltffi-linux-aarch64-musl.tar.gz.sha256` |
| macOS x86\_64        | `boltffi-darwin-x86_64.tar.gz`      | `boltffi-darwin-x86_64.tar.gz.sha256`      |
| macOS ARM64          | `boltffi-darwin-aarch64.tar.gz`     | `boltffi-darwin-aarch64.tar.gz.sha256`     |
| Windows x86\_64      | `boltffi-windows-x86_64.zip`        | `boltffi-windows-x86_64.zip.sha256`        |
| Windows ARM64        | `boltffi-windows-arm64.zip`         | `boltffi-windows-arm64.zip.sha256`         |

For example, download and verify the Windows ARM64 build in PowerShell. Replace `<VERSION>` with the release version without the leading `v`:

```powershell
$version = "<VERSION>"
$asset = "boltffi-windows-arm64.zip"
$release = "https://github.com/boltffi/boltffi/releases/download/v$version"

Invoke-WebRequest "$release/$asset" -OutFile $asset
Invoke-WebRequest "$release/$asset.sha256" -OutFile "$asset.sha256"

$expected = (Get-Content "$asset.sha256" -Raw).Trim().Split()[0]
$actual = (Get-FileHash $asset -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actual -ne $expected) {
    throw "Checksum verification failed"
}

Expand-Archive $asset -DestinationPath .
.\boltffi.exe check
```

## Add to your project

Add BoltFFI to your library crate:

```bash
cargo add boltffi
cargo add --build boltffi
```

Then configure your library crate:

```toml
[package]
name = "mylib"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["staticlib"]
```

Add `cdylib` if you also need a standalone Rust shared library outside BoltFFI packaging, including the current C# generate-and-integrate flow:

```toml
[lib]
crate-type = ["staticlib", "cdylib"]
```

## Create build.rs

Create a `build.rs` file in your project root:

```rust
fn main() {
    boltffi::build::generate();
}
```

## Verify installation

```bash
boltffi check
```

This verifies you have the required tools and Rust targets installed. Run `boltffi check --fix` to auto-install missing targets.

---

Source: https://boltffi.dev/docs/quick-start

# Quick Start

Build your first BoltFFI project in 5 minutes.

## 1. Create a new library

```bash
cargo new --lib mylib
cd mylib
```

## 2. Configure Cargo.toml

```toml
[package]
name = "mylib"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["staticlib", "cdylib"]
```

Install BoltFFI:

```bash
cargo add boltffi
cargo add --build boltffi
```

## 3. Create build.rs

```rust
fn main() {
    boltffi::build::generate();
}
```

## 4. Write your Rust code

Replace `src/lib.rs`:

```rust
use boltffi::{data, export};

#[data]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

#[export]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[export]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

#[export]
pub fn distance(a: Point, b: Point) -> f64 {
    ((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt()
}
```

## 5. Build and generate bindings

```bash
# Initialize config
boltffi init

# Build for iOS
boltffi build ios --release

# Generate Swift bindings
boltffi generate swift

# Package as XCFramework
boltffi pack ios

# Or build the NuGet package for .NET
boltffi pack csharp
```

## 6. Use from Swift

```swift
let sum = add(a: 2, b: 3)
print("2 + 3 = \(sum)")  // "2 + 3 = 5"

let message = greet(name: "World")
print(message)  // "Hello, World!"

let p1 = Point(x: 0, y: 0)
let p2 = Point(x: 3, y: 4)
print("Distance: \(distance(a: p1, b: p2))")  // "Distance: 5.0"
```

## 7. Use from C\#

```csharp
int sum = MyLib.Add(2, 3);
Console.WriteLine($"2 + 3 = {sum}");

string message = MyLib.Greet("World");
Console.WriteLine(message);

Point p1 = new Point(0.0, 0.0);
Point p2 = new Point(3.0, 4.0);
Console.WriteLine(MyLib.Distance(p1, p2));
```

---

Source: https://boltffi.dev/docs/tutorial

# Tutorial

This tutorial walks through building a counter library in Rust and using it from Swift, Kotlin, and C#.

## Create the Rust library

```bash
cargo new --lib counter
cd counter
```

Add BoltFFI to `Cargo.toml`:

```toml
[package]
name = "counter"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["staticlib", "cdylib"]
```

Install BoltFFI:

```bash
cargo add boltffi
```

## Write the Rust code

Create `src/lib.rs`:

```rust
use boltffi::*;
use std::sync::atomic::{AtomicI64, Ordering};

pub struct Counter {
    value: AtomicI64,
}

#[export]
impl Counter {
    pub fn new(initial: i64) -> Self {
        Counter {
            value: AtomicI64::new(initial),
        }
    }

    pub fn increment(&self) -> i64 {
        self.value.fetch_add(1, Ordering::SeqCst) + 1
    }

    pub fn decrement(&self) -> i64 {
        self.value.fetch_sub(1, Ordering::SeqCst) - 1
    }

    pub fn get(&self) -> i64 {
        self.value.load(Ordering::SeqCst)
    }

    pub fn reset(&self) {
        self.value.store(0, Ordering::SeqCst);
    }
}
```

The `#[export]` attribute tells BoltFFI to generate bindings for `Counter`. The struct uses `AtomicI64` for thread-safe access without locks.

## Generate bindings

For Apple (Swift):

```bash
boltffi pack apple
```

For Android (Kotlin):

```bash
boltffi pack android
```

For C#:

```bash
boltffi pack csharp
```

This produces a `.nupkg` in `dist/csharp/packages/` that you can reference from your .NET project as a local NuGet feed. The package bundles the bindings and the native runtime assets together.

## Use from Swift

```swift
import Counter

let counter = Counter(initial: 0)

print(counter.increment())  // 1
print(counter.increment())  // 2
print(counter.get())        // 2
print(counter.decrement())  // 1
counter.reset()
print(counter.get())        // 0
```

## Use from Kotlin

```kotlin
import com.example.counter.Counter

val counter = Counter(initial = 0)

println(counter.increment())  // 1
println(counter.increment())  // 2
println(counter.get())        // 2
println(counter.decrement())  // 1
counter.reset()
println(counter.get())        // 0
```

## Use from C\#

```csharp
using Counter counter = new Counter(0);

Console.WriteLine(counter.Increment());  // 1
Console.WriteLine(counter.Increment());  // 2
Console.WriteLine(counter.Get());        // 2
Console.WriteLine(counter.Decrement());  // 1
counter.Reset();
Console.WriteLine(counter.Get());        // 0
```

## Adding error handling

Extend the counter to reject negative values:

```rust
use boltffi::*;
use std::sync::atomic::{AtomicI64, Ordering};

#[error]
pub struct CounterError {
    pub message: String,
}

pub struct Counter {
    value: AtomicI64,
    min: i64,
}

#[export]
impl Counter {
    pub fn new(initial: i64, min: i64) -> Result<Self, CounterError> {
        if initial < min {
            return Err(CounterError {
                message: format!("initial {} below minimum {}", initial, min),
            });
        }
        Ok(Counter {
            value: AtomicI64::new(initial),
            min,
        })
    }

    pub fn decrement(&self) -> Result<i64, CounterError> {
        let current = self.value.load(Ordering::SeqCst);
        if current <= self.min {
            return Err(CounterError {
                message: format!("cannot go below {}", self.min),
            });
        }
        Ok(self.value.fetch_sub(1, Ordering::SeqCst) - 1)
    }
}
```

In Swift, the error becomes a thrown exception:

```swift
do {
    let counter = try Counter(initial: 0, min: 0)
    try counter.decrement()  // throws CounterError
} catch let error as CounterError {
    print(error.message)
}
```

In Kotlin:

```kotlin
try {
    val counter = Counter(initial = 0, min = 0)
    counter.decrement()  // throws CounterError
} catch (e: CounterError) {
    println(e.message)
}
```

In C#:

```csharp
try {
    using Counter counter = new Counter(0, 0);
    counter.Decrement();  // throws CounterErrorException
} catch (CounterErrorException e) {
    Console.WriteLine(e.Error.Message);
}
```

## Adding async

Make the counter persist to a file asynchronously:

```rust
use boltffi::*;
use std::sync::atomic::{AtomicI64, Ordering};
use std::path::PathBuf;

pub struct PersistentCounter {
    value: AtomicI64,
    path: PathBuf,
}

#[export]
impl PersistentCounter {
    pub fn new(path: String) -> Self {
        PersistentCounter {
            value: AtomicI64::new(0),
            path: PathBuf::from(path),
        }
    }

    pub fn increment(&self) -> i64 {
        self.value.fetch_add(1, Ordering::SeqCst) + 1
    }

    pub async fn save(&self) -> Result<(), String> {
        let value = self.value.load(Ordering::SeqCst);
        tokio::fs::write(&self.path, value.to_string().as_bytes())
            .await
            .map_err(|e| e.to_string())
    }

    pub async fn load(&self) -> Result<i64, String> {
        let contents = tokio::fs::read_to_string(&self.path)
            .await
            .map_err(|e| e.to_string())?;
        let value: i64 = contents.trim().parse().map_err(|e: std::num::ParseIntError| e.to_string())?;
        self.value.store(value, Ordering::SeqCst);
        Ok(value)
    }
}
```

In Swift, async methods use structured concurrency:

```swift
let counter = PersistentCounter(path: "/tmp/counter.txt")
counter.increment()
counter.increment()

try await counter.save()
let loaded = try await counter.load()
print(loaded)  // 2
```

In Kotlin, they become suspend functions:

```kotlin
val counter = PersistentCounter(path = "/tmp/counter.txt")
counter.increment()
counter.increment()

counter.save()
val loaded = counter.load()
println(loaded)  // 2
```

In C#, they return `Task`:

```csharp
using PersistentCounter counter =
    new PersistentCounter("/tmp/counter.txt");
counter.Increment();
counter.Increment();

await counter.Save();
long loaded = await counter.Load();
Console.WriteLine(loaded);  // 2
```

## Next steps

See [Types](/docs/types.md) for the full list of supported types, [Async](/docs/async.md) for async patterns, and [Streaming](/docs/streaming.md) for real-time data.

---

Source: https://boltffi.dev/docs/types

# Types

When you design a Rust API for BoltFFI, you need to know what happens to your types on the other side. This page shows the mapping for each target language.

## Quick Reference

#### Numeric Types

**Integers**

| Rust  | Swift    | Kotlin   | Java    | C#       | TypeScript |
| ----- | -------- | -------- | ------- | -------- | ---------- |
| `i8`  | `Int8`   | `Byte`   | `byte`  | `sbyte`  | `number`   |
| `i16` | `Int16`  | `Short`  | `short` | `short`  | `number`   |
| `i32` | `Int32`  | `Int`    | `int`   | `int`    | `number`   |
| `i64` | `Int64`  | `Long`   | `long`  | `long`   | `bigint`   |
| `u8`  | `UInt8`  | `UByte`  | `byte`  | `byte`   | `number`   |
| `u16` | `UInt16` | `UShort` | `short` | `ushort` | `number`   |
| `u32` | `UInt32` | `UInt`   | `int`   | `uint`   | `number`   |
| `u64` | `UInt64` | `ULong`  | `long`  | `ulong`  | `bigint`   |

**Floats & Primitives**

| Rust     | Swift    | Kotlin    | Java      | C#       | TypeScript |
| -------- | -------- | --------- | --------- | -------- | ---------- |
| `f32`    | `Float`  | `Float`   | `float`   | `float`  | `number`   |
| `f64`    | `Double` | `Double`  | `double`  | `double` | `number`   |
| `bool`   | `Bool`   | `Boolean` | `boolean` | `bool`   | `boolean`  |
| `String` | `String` | `String`  | `String`  | `string` | `string`   |

#### Collections & Wrappers

| Rust           | Swift    | Kotlin    | Java           | C#       | TypeScript  |
| -------------- | -------- | --------- | -------------- | -------- | ----------- |
| `Vec<T>`       | `[T]`    | `List<T>` | `List<T>`      | `T[]`    | `T[]`       |
| `Option<T>`    | `T?`     | `T?`      | `T (nullable)` | `T?`     | `T \| null` |
| `Result<T, E>` | `throws` | `throws`  | `throws`       | `throws` | `throws`    |

#### Built-in Custom Types

| Rust         | Swift          | Kotlin      | Java       | C#       | TypeScript   |
| ------------ | -------------- | ----------- | ---------- | -------- | ------------ |
| `Duration`   | `TimeInterval` | `Duration`  | `Duration` | `long`   | `Duration`   |
| `SystemTime` | `Date`         | `Instant`   | `Instant`  | `long`   | `Date`       |
| `Uuid`       | `UUID`         | `UUID`      | `UUID`     | `string` | `string`     |
| `Url`        | `URL`          | `URI`       | `URI`      | `string` | `string`     |
| `Vec<u8>`    | `Data`         | `ByteArray` | `byte[]`   | `byte[]` | `Uint8Array` |

## Primitives

Primitives are the foundation of BoltFFI's performance. When you pass an `i32` or `f64` across the FFI boundary, the bytes copy directly. There's no serialization, no intermediate buffer, no allocation. The value moves from one side to the other as raw memory.

This matters because every FFI call has overhead. If you're making thousands of calls per second, or processing real-time data, that overhead adds up. By keeping your hot-path types primitive, you minimize the cost of each crossing.

Choose your integer size deliberately. `i32` handles most application logic. `i64` is necessary for timestamps, file sizes, or any value that might exceed 2 billion. `u8` is the right choice for byte buffers, binary protocols, or any raw data manipulation. Using the smallest size that fits your data isn't premature optimization here; it directly affects how much memory moves across the boundary.

**Integer sizes**

**Rust**

```rust
#[export]
pub fn add(a: i32, b: i32) -> i32 {
  a + b
}

#[export]
pub fn timestamp_ms() -> i64 {
  std::time::SystemTime::now()
      .duration_since(std::time::UNIX_EPOCH)
      .unwrap()
      .as_millis() as i64
}

#[export]
pub fn byte_at(
  data: &[u8],
  index: usize
) -> u8 {
  data[index]
}
```

**Swift**

```swift
func add(a: Int32, b: Int32) -> Int32
func timestampMs() -> Int64
func byteAt(data: [UInt8], index: UInt) -> UInt8

let sum = add(a: 5, b: 3)
let ts = timestampMs()
let b = byteAt(data: [0x48, 0x69], index: 0)
```

**Kotlin**

```kotlin
fun add(a: Int, b: Int): Int
fun timestampMs(): Long
fun byteAt(data: ByteArray, index: ULong): UByte

val sum = add(5, 3)
val ts = timestampMs()
val b = byteAt(byteArrayOf(0x48, 0x69), 0uL)
```

**Java**

```java
static int add(int a, int b)
static long timestampMs()
static byte byteAt(byte[] data, long index)

int sum = add(5, 3);
long ts = timestampMs();
byte b = byteAt(new byte[]{0x48, 0x69}, 0L);
```

**C#**

```csharp
static int Add(int a, int b)
static long TimestampMs()
static byte ByteAt(byte[] data, nuint index)

int sum = MyLib.Add(5, 3);
long ts = MyLib.TimestampMs();
byte b = MyLib.ByteAt(new byte[] { 0x48, 0x69 }, 0);
```

**TypeScript**

```typescript
function add(a: number, b: number): number
function timestampMs(): bigint
function byteAt(data: Uint8Array, index: number): number

const sum = add(5, 3)
const ts = timestampMs()
const b = byteAt(new Uint8Array([0x48, 0x69]), 0)
```

**Python**

```python
def add(a: int, b: int) -> int: ...
def timestamp_ms() -> int: ...
def byte_at(data: bytes, index: int) -> int: ...

sum_value = add(5, 3)
ts = timestamp_ms()
b = byte_at(bytes([0x48, 0x69]), 0)
```

For floating point, `f64` is the default choice. It matches what most languages use for their default float type, and the extra precision rarely hurts. Use `f32` when you're interfacing with graphics APIs, audio processing, or other domains where single precision is the standard.

**Floating point**

**Rust**

```rust
#[export]
pub fn circle_area(radius: f64) -> f64 {
  std::f64::consts::PI * radius * radius
}

#[export]
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
  a + (b - a) * t
}
```

**Swift**

```swift
func circleArea(radius: Double) -> Double
func lerp(a: Float, b: Float, t: Float) -> Float

let area = circleArea(radius: 5.0)
let mid = lerp(a: 0.0, b: 10.0, t: 0.5)
```

**Kotlin**

```kotlin
fun circleArea(radius: Double): Double
fun lerp(a: Float, b: Float, t: Float): Float

val area = circleArea(5.0)
val mid = lerp(0.0f, 10.0f, 0.5f)
```

**Java**

```java
static double circleArea(double radius)
static float lerp(float a, float b, float t)

double area = circleArea(5.0);
float mid = lerp(0.0f, 10.0f, 0.5f);
```

**C#**

```csharp
static double CircleArea(double radius)
static float Lerp(float a, float b, float t)

double area = MyLib.CircleArea(5.0);
float mid = MyLib.Lerp(0.0f, 10.0f, 0.5f);
```

**TypeScript**

```typescript
function circleArea(radius: number): number
function lerp(a: number, b: number, t: number): number

const area = circleArea(5.0)
const mid = lerp(0.0, 10.0, 0.5)
```

**Python**

```python
def circle_area(radius: float) -> float: ...
def lerp(a: float, b: float, t: float) -> float: ...

area = circle_area(5.0)
mid = lerp(0.0, 10.0, 0.5)
```

## Strings

Strings are where the FFI cost becomes visible. Unlike primitives, strings require memory allocation and copying. Rust's string data lives in Rust's heap; the target language's string lives in its own heap. Crossing the boundary means allocating new memory and copying bytes.

Both `&str` and `String` on the Rust side become owned strings in the target language. There's no way to pass a reference that the target language can use without copying, because Rust's memory model doesn't extend across the FFI boundary. The target language needs its own copy that it can manage with its own garbage collector or reference counting.

The practical rule: use `&str` for parameters (you're borrowing the caller's data) and `String` for return values (you're transferring ownership). This matches idiomatic Rust and works cleanly with BoltFFI's generated code.

**String parameters and returns**

**Rust**

```rust
#[export]
pub fn greet(name: &str) -> String {
  format!("Hello, {}!", name)
}

#[export]
pub fn repeat(s: &str, n: usize) -> String {
  s.repeat(n)
}

#[export]
pub fn first_word(s: &str) -> String {
  s.split_whitespace()
      .next()
      .unwrap_or("")
      .to_string()
}
```

**Swift**

```swift
func greet(name: String) -> String
func repeat(s: String, n: UInt) -> String
func firstWord(s: String) -> String

let msg = greet(name: "World")
let ha = repeat(s: "ha", n: 3)  // "hahaha"
let word = firstWord(s: "hello world")
```

**Kotlin**

```kotlin
fun greet(name: String): String
fun repeat(s: String, n: ULong): String
fun firstWord(s: String): String

val msg = greet("World")
val ha = repeat("ha", 3uL)  // "hahaha"
val word = firstWord("hello world")
```

**Java**

```java
static String greet(String name)
static String repeat(String s, long n)
static String firstWord(String s)

String msg = greet("World");
String ha = repeat("ha", 3L);  // "hahaha"
String word = firstWord("hello world");
```

**C#**

```csharp
static string Greet(string name)
static string Repeat(string s, nuint n)
static string FirstWord(string s)

string msg = MyLib.Greet("World");
string ha = MyLib.Repeat("ha", 3);  // "hahaha"
string word = MyLib.FirstWord("hello world");
```

**TypeScript**

```typescript
function greet(name: string): string
function repeat(s: string, n: number): string
function firstWord(s: string): string

const msg = greet("World")
const ha = repeat("ha", 3)  // "hahaha"
const word = firstWord("hello world")
```

**Python**

```python
def greet(name: str) -> str: ...
def repeat(s: str, n: int) -> str: ...
def first_word(s: str) -> str: ...

msg = greet("World")
ha = repeat("ha", 3)  # "hahaha"
word = first_word("hello world")
```

If string handling is your bottleneck, rethink the API. Instead of calling a function once per string in a loop, pass a batch of strings and process them all in one call. Or do the string-heavy work entirely on the Rust side and only return the final result.

## Records

Records are value types: structs and enums marked with `#[data]`. When a record crosses the boundary, it gets copied. The target language receives its own copy of the data.

The cost depends on the contents: primitive-only records are fast, records with strings or collections require allocations.

```rust
#[data]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

#[data]
pub enum Status {
    Pending,
    Active,
    Done,
}
```

See [Records](/docs/records.md) for nested structs, default values, enums with data, and performance characteristics.

## Classes

Classes are reference types. The object lives in Rust, and the target language holds a handle to it. Use classes for stateful objects, resources, or anything with methods.

```rust
pub struct Counter { value: i32 }

#[export]
impl Counter {
    pub fn new() -> Self { Counter { value: 0 } }
    pub fn increment(&mut self) { self.value += 1; }
    pub fn get(&self) -> i32 { self.value }
}
```

See [Classes](/docs/classes.md) for constructors, methods, thread safety, and memory management.

## Option

`Option<T>` in Rust becomes a nullable type in the target language. `None` becomes `null` or `nil` on the other side.

**Rust**

```rust
#[export]
pub fn find_user(id: u64) -> Option<String> {
  if id == 42 {
      Some("alice".to_string())
  } else {
      None
  }
}

#[export]
pub fn parse_int(s: &str) -> Option<i32> {
  s.parse().ok()
}
```

**Swift**

```swift
func findUser(id: UInt64) -> String?
func parseInt(s: String) -> Int32?

let user = findUser(id: 42)  // Optional("alice")
let num = parseInt(s: "123") // Optional(123)
let bad = parseInt(s: "abc") // nil
```

**Kotlin**

```kotlin
fun findUser(id: ULong): String?
fun parseInt(s: String): Int?

val user = findUser(42uL)  // "alice"
val num = parseInt("123")  // 123
val bad = parseInt("abc")  // null
```

**Java**

```java
static String findUser(long id)
static java.util.Optional<Integer> parseInt(String s)

String user = findUser(42L);  // "alice"
Optional<Integer> num = parseInt("123"); // 123
Optional<Integer> bad = parseInt("abc"); // empty
```

**C#**

```csharp
static string? FindUser(ulong id)
static int? ParseInt(string s)

string? user = MyLib.FindUser(42UL);  // "alice"
int? num = MyLib.ParseInt("123");     // 123
int? bad = MyLib.ParseInt("abc");     // null
```

**TypeScript**

```typescript
function findUser(id: bigint): string | null
function parseInt(s: string): number | null

const user = findUser(42n)  // "alice"
const num = parseInt("123") // 123
const bad = parseInt("abc") // null
```

**Python**

```python
def find_user(id: int) -> str | None: ...
def parse_int(s: str) -> int | None: ...

user = find_user(42)      # "alice"
num = parse_int("123")    # 123
bad = parse_int("abc")    # None
```

## Result and errors

`Result<T, E>` becomes a throwing function. The `Ok` value is returned normally; the `Err` value becomes a thrown exception. The error type must be marked with `#[error]`.

**Rust**

```rust
#[error]
pub enum ParseError {
  Empty,
  InvalidFormat,
  OutOfRange,
}

#[export]
pub fn parse_port(s: &str) -> Result<u16, ParseError> {
  if s.is_empty() {
      return Err(ParseError::Empty);
  }
  let n: i32 = s.parse()
      .map_err(|_| ParseError::InvalidFormat)?;
  if n < 0 || n > 65535 {
      return Err(ParseError::OutOfRange);
  }
  Ok(n as u16)
}
```

**Swift**

```swift
public enum ParseError: Error {
  case empty
  case invalidFormat
  case outOfRange
}

func parsePort(s: String) throws -> UInt16

do {
  let port = try parsePort(s: "8080")
} catch ParseError.empty {
  print("empty input")
} catch {
  print("other error")
}
```

**Kotlin**

```kotlin
sealed class ParseError : Exception() {
  object Empty : ParseError()
  object InvalidFormat : ParseError()
  object OutOfRange : ParseError()
}

@Throws(ParseError::class)
fun parsePort(s: String): UShort

try {
  val port = parsePort("8080")
} catch (e: ParseError.Empty) {
  println("empty input")
} catch (e: ParseError) {
  println("other error")
}
```

**Java**

```java
public enum ParseError {
  EMPTY(0),
  INVALID_FORMAT(1),
  OUT_OF_RANGE(2);

  public final int value;
}

static short parsePort(String s) // throws

try {
  short port = parsePort("8080");
} catch (RuntimeException e) {
  System.out.println(e.getMessage());
}
```

**C#**

```csharp
public enum ParseError
{
  Empty = 0,
  InvalidFormat = 1,
  OutOfRange = 2,
}

static ushort ParsePort(string s) // throws

try {
  ushort port = MyLib.ParsePort("8080");
} catch (ParseErrorException e) {
  Console.WriteLine(e.Error);
}
```

**TypeScript**

```typescript
enum ParseError {
  Empty = 0,
  InvalidFormat = 1,
  OutOfRange = 2
}

class ParseErrorException extends Error {
  readonly code: ParseError
}

function parsePort(s: string): number // throws

try {
  const port = parsePort("8080")
} catch (e) {
  if (e instanceof ParseErrorException && e.code === ParseError.Empty) {
      console.log("empty input")
  } else {
      console.log("other error")
  }
}
```

**Python**

```python
from enum import IntEnum

class ParseError(IntEnum):
  EMPTY = 0
  INVALID_FORMAT = 1
  OUT_OF_RANGE = 2

class ParseErrorException(RuntimeError):
  error: ParseError

def parse_port(s: str) -> int: ...

try:
  port = parse_port("8080")
except ParseErrorException as error:
  print(error.error)
```

## Collections

Collections let you pass multiple values in a single call. `Vec<T>` in Rust becomes a native array or list in the target language. Slices (`&[T]`) work the same way for input parameters.

The cost scales with the number of elements. A `Vec<i32>` with 1000 elements moves 1000 integers across the boundary. A `Vec<User>` with 1000 users moves 1000 structs, each with their own strings and fields. For large collections of complex types, this can become the dominant cost in your FFI call.

If performance matters, prefer collections of primitives over collections of complex types.

### Basic collections

**Rust**

```rust
#[export]
pub fn sum(values: &[i32]) -> i32 {
  values.iter().sum()
}

#[export]
pub fn range(start: i32, end: i32) -> Vec<i32> {
  (start..end).collect()
}

#[export]
pub fn filter_positive(
  values: &[i32]
) -> Vec<i32> {
  values.iter()
      .copied()
      .filter(|&x| x > 0)
      .collect()
}
```

**Swift**

```swift
func sum(values: [Int32]) -> Int32
func range(start: Int32, end: Int32) -> [Int32]
func filterPositive(values: [Int32]) -> [Int32]

let total = sum(values: [1, 2, 3, 4, 5])
let nums = range(start: 0, end: 10)
let pos = filterPositive(values: [-1, 2, -3, 4])
```

**Kotlin**

```kotlin
fun sum(values: IntArray): Int
fun range(start: Int, end: Int): IntArray
fun filterPositive(values: IntArray): IntArray

val total = sum(intArrayOf(1, 2, 3, 4, 5))
val nums = range(0, 10)
val pos = filterPositive(intArrayOf(-1, 2, -3, 4))
```

**Java**

```java
static int sum(int[] values)
static int[] range(int start, int end)
static int[] filterPositive(int[] values)

int total = sum(new int[]{1, 2, 3, 4, 5});
int[] nums = range(0, 10);
int[] pos = filterPositive(new int[]{-1, 2, -3, 4});
```

**C#**

```csharp
static int Sum(int[] values)
static int[] Range(int start, int end)
static int[] FilterPositive(int[] values)

int total = MyLib.Sum(new[] { 1, 2, 3, 4, 5 });
int[] nums = MyLib.Range(0, 10);
int[] pos = MyLib.FilterPositive(new[] { -1, 2, -3, 4 });
```

**TypeScript**

```typescript
function sum(values: number[]): number
function range(start: number, end: number): number[]
function filterPositive(values: number[]): number[]

const total = sum([1, 2, 3, 4, 5])
const nums = range(0, 10)
const pos = filterPositive([-1, 2, -3, 4])
```

**Python**

```python
def sum(values: list[int]) -> int: ...
def range(start: int, end: int) -> list[int]: ...
def filter_positive(values: list[int]) -> list[int]: ...

total = sum([1, 2, 3, 4, 5])
nums = range(0, 10)
pos = filter_positive([-1, 2, -3, 4])
```

### Nested collections

`Vec<Vec<T>>` and deeper nesting are supported. The deeper you nest, the more work it takes to move across the boundary.

**Rust**

```rust
#[export]
pub fn transpose(
  matrix: &[Vec<i32>]
) -> Vec<Vec<i32>> {
  if matrix.is_empty() {
      return vec![];
  }
  let rows = matrix.len();
  let cols = matrix[0].len();
  (0..cols)
      .map(|c| {
          (0..rows)
              .map(|r| matrix[r][c])
              .collect()
      })
      .collect()
}
```

**Swift**

```swift
func transpose(matrix: [[Int32]]) -> [[Int32]]

let m = [[1, 2, 3], [4, 5, 6]]
let t = transpose(matrix: m)
// [[1, 4], [2, 5], [3, 6]]
```

**Kotlin**

```kotlin
fun transpose(matrix: List<IntArray>): List<IntArray>

val m = listOf(intArrayOf(1, 2, 3), intArrayOf(4, 5, 6))
val t = transpose(m)
// [[1, 4], [2, 5], [3, 6]]
```

**Java**

```java
static List<int[]> transpose(List<int[]> matrix)

List<int[]> m = List.of(
  new int[]{1, 2, 3},
  new int[]{4, 5, 6}
);
List<int[]> t = transpose(m);
// [[1, 4], [2, 5], [3, 6]]
```

**C#**

```csharp
static int[][] Transpose(int[][] matrix)

int[][] m = {
  new[] { 1, 2, 3 },
  new[] { 4, 5, 6 },
};
int[][] t = MyLib.Transpose(m);
// [[1, 4], [2, 5], [3, 6]]
```

**TypeScript**

```typescript
function transpose(matrix: number[][]): number[][]

const m = [[1, 2, 3], [4, 5, 6]]
const t = transpose(m)
// [[1, 4], [2, 5], [3, 6]]
```

**Python**

```python
def transpose(matrix: list[list[int]]) -> list[list[int]]: ...

m = [[1, 2, 3], [4, 5, 6]]
t = transpose(m)
# [[1, 4], [2, 5], [3, 6]]
```

## Callbacks

Pass functions from the target language into Rust. Use `impl Fn`, `impl FnMut`, or `impl FnOnce` for simple callbacks.

```rust
#[export]
pub fn foreach_range(start: i32, end: i32, mut cb: impl FnMut(i32)) {
    (start..end).for_each(|i| cb(i));
}
```

For callbacks with multiple methods or that need to be stored, use callback traits with `#[export]`.

```rust
#[export]
pub trait Logger {
    fn log(&self, message: &str);
    fn flush(&self);
}
```

See [Callbacks](/docs/callbacks.md) for closures, callback traits, and async callbacks.

## What's not supported

Some Rust types can't cross the FFI boundary. This isn't a limitation of BoltFFI specifically; these types don't have meaningful representations in other languages, or would require runtime support that doesn't exist.

- Generic structs like `struct Wrapper<T>` require monomorphization. Define concrete types like `struct StringWrapper { value: String }` instead.

- Trait objects like `dyn Trait` rely on Rust's vtable mechanism. Use an enum with variants for each concrete type you need to support.

- Raw pointers are inherently unsafe. Handle pointer manipulation inside Rust and expose safe types at the boundary.

- Non-static lifetimes like `&'a str` can't be enforced across FFI. Return owned data (`String`) instead of borrowed references.

- HashSet doesn't have a universal representation. Convert to `Vec<T>` instead.

## Built-in custom types

BoltFFI recognizes certain standard library and common crate types and maps them to idiomatic equivalents in each target language.

**Built-in Type Mappings**

| Rust                    | Swift          | Kotlin      | Java       | C#       | TypeScript   |
| ----------------------- | -------------- | ----------- | ---------- | -------- | ------------ |
| `std::time::Duration`   | `TimeInterval` | `Duration`  | `Duration` | `long`   | `Duration`   |
| `std::time::SystemTime` | `Date`         | `Instant`   | `Instant`  | `long`   | `Date`       |
| `uuid::Uuid`            | `UUID`         | `UUID`      | `UUID`     | `string` | `string`     |
| `url::Url`              | `URL`          | `URI`       | `URI`      | `string` | `string`     |
| `Vec<u8>`               | `Data`         | `ByteArray` | `byte[]`   | `byte[]` | `Uint8Array` |

### Duration

`std::time::Duration` represents a span of time. BoltFFI encodes it as seconds plus nanoseconds (12 bytes total). Swift uses `TimeInterval` (a `Double` of seconds), Kotlin and Java use `java.time.Duration`, C# uses a `long`, TypeScript uses a `{ secs: bigint, nanos: number }` object, and Python uses seconds as a `float`.

**Rust**

```rust
use std::time::Duration;

#[export]
pub fn sleep_duration() -> Duration {
  Duration::from_millis(500)
}

#[export]
pub fn double_duration(d: Duration) -> Duration {
  d * 2
}
```

**Swift**

```swift
func sleepDuration() -> TimeInterval
func doubleDuration(d: TimeInterval) -> TimeInterval

let half = sleepDuration()  // 0.5
let full = doubleDuration(d: half)  // 1.0
```

**Kotlin**

```kotlin
fun sleepDuration(): Duration
fun doubleDuration(d: Duration): Duration

val half = sleepDuration()  // PT0.5S
val full = doubleDuration(half)  // PT1S
```

**Java**

```java
static Duration sleepDuration()
static Duration doubleDuration(Duration d)

Duration half = sleepDuration();
Duration full = doubleDuration(half);
```

**C#**

```csharp
static long SleepDuration()
static long DoubleDuration(long d)

long half = MyLib.SleepDuration();
long full = MyLib.DoubleDuration(half);
```

**TypeScript**

```typescript
function sleepDuration(): Duration
function doubleDuration(d: Duration): Duration

const half = sleepDuration()  // { secs: 0n, nanos: 500000000 }
const full = doubleDuration(half)
```

**Python**

```python
def sleep_duration() -> float: ...
def double_duration(d: float) -> float: ...

half = sleep_duration()  # 0.5
full = double_duration(half)
```

### SystemTime

`std::time::SystemTime` represents a point in time. BoltFFI encodes it as seconds since Unix epoch plus nanoseconds (12 bytes). Swift uses `Date`, Kotlin and Java use `Instant`, C# uses a `long`, TypeScript uses `Date`, and Python uses Unix seconds as a `float`. Negative values represent times before 1970.

**Rust**

```rust
use std::time::SystemTime;

#[export]
pub fn now() -> SystemTime {
  SystemTime::now()
}

#[export]
pub fn epoch() -> SystemTime {
  SystemTime::UNIX_EPOCH
}
```

**Swift**

```swift
func now() -> Date
func epoch() -> Date

let current = now()
let unix = epoch()  // 1970-01-01
```

**Kotlin**

```kotlin
fun now(): Instant
fun epoch(): Instant

val current = now()
val unix = epoch()  // 1970-01-01T00:00:00Z
```

**Java**

```java
static Instant now()
static Instant epoch()

Instant current = now();
Instant unix = epoch();
```

**C#**

```csharp
static long Now()
static long Epoch()

long current = MyLib.Now();
long unix = MyLib.Epoch();
```

**TypeScript**

```typescript
function now(): Date
function epoch(): Date

const current = now()
const unix = epoch()  // 1970-01-01T00:00:00.000Z
```

**Python**

```python
def now() -> float: ...
def epoch() -> float: ...

current = now()
unix = epoch()  # 0.0
```

### UUID

`uuid::Uuid` from the [uuid](https://crates.io/crates/uuid) crate maps to native UUID types in Swift, Kotlin, Java, and Python. C# and TypeScript receive it as a string since those bindings do not use a generated UUID wrapper.

**Rust**

```rust
use uuid::Uuid;

#[export]
pub fn new_id() -> Uuid {
  Uuid::new_v4()
}

#[export]
pub fn parse_id(s: &str) -> Option<Uuid> {
  Uuid::parse_str(s).ok()
}
```

**Swift**

```swift
func newId() -> UUID
func parseId(s: String) -> UUID?

let id = newId()
let parsed = parseId(s: "550e8400-e29b-41d4-a716-446655440000")
```

**Kotlin**

```kotlin
fun newId(): UUID
fun parseId(s: String): UUID?

val id = newId()
val parsed = parseId("550e8400-e29b-41d4-a716-446655440000")
```

**Java**

```java
static UUID newId()
static UUID parseId(String s)

UUID id = newId();
UUID parsed = parseId("550e8400-e29b-41d4-a716-446655440000");
```

**C#**

```csharp
static string NewId()
static string? ParseId(string s)

string id = MyLib.NewId();
string? parsed = MyLib.ParseId("550e8400-e29b-41d4-a716-446655440000");
```

**TypeScript**

```typescript
function newId(): string
function parseId(s: string): string | null

const id = newId()  // "550e8400-e29b-41d4-a716-446655440000"
const parsed = parseId("550e8400-e29b-41d4-a716-446655440000")
```

**Python**

```python
def new_id() -> uuid.UUID: ...
def parse_id(s: str) -> uuid.UUID | None: ...

id = new_id()
parsed = parse_id("550e8400-e29b-41d4-a716-446655440000")
```

### URL

`url::Url` from the [url](https://crates.io/crates/url) crate maps to `URL` in Swift, `URI` in Kotlin and Java, and `string` in C# and TypeScript. The URL is serialized as its string representation.

**Rust**

```rust
use url::Url;

#[export]
pub fn base_url() -> Url {
  Url::parse("https://example.com").unwrap()
}

#[export]
pub fn with_path(base: Url, path: &str) -> Url {
  base.join(path).unwrap()
}
```

**Swift**

```swift
func baseUrl() -> URL
func withPath(base: URL, path: String) -> URL

let base = baseUrl()
let full = withPath(base: base, path: "/api/v1")
```

**Kotlin**

```kotlin
fun baseUrl(): URI
fun withPath(base: URI, path: String): URI

val base = baseUrl()
val full = withPath(base, "/api/v1")
```

**Java**

```java
static URI baseUrl()
static URI withPath(URI base, String path)

URI base = baseUrl();
URI full = withPath(base, "/api/v1");
```

**C#**

```csharp
static string BaseUrl()
static string WithPath(string baseUrl, string path)

string baseUrl = MyLib.BaseUrl();  // "https://example.com/"
string full = MyLib.WithPath(baseUrl, "/api/v1");
```

**TypeScript**

```typescript
function baseUrl(): string
function withPath(base: string, path: string): string

const base = baseUrl()  // "https://example.com/"
const full = withPath(base, "/api/v1")
```

**Python**

```python
def base_url() -> str: ...
def with_path(base: str, path: str) -> str: ...

base = base_url()  # "https://example.com/"
full = with_path(base, "/api/v1")
```

### Bytes

`Vec<u8>` and `&[u8]` represent raw binary data. Unlike other collections, bytes get special treatment: they map to `Data` in Swift, `ByteArray` in Kotlin, `byte[]` in Java and C#, and `Uint8Array` in TypeScript.

**Rust**

```rust
#[export]
pub fn compress(data: &[u8]) -> Vec<u8> {
  // compression logic
  data.to_vec()
}

#[export]
pub fn hash(data: &[u8]) -> Vec<u8> {
  // hashing logic
  vec![0u8; 32]
}
```

**Swift**

```swift
func compress(data: Data) -> Data
func hash(data: Data) -> Data

let compressed = compress(data: originalData)
let digest = hash(data: message)
```

**Kotlin**

```kotlin
fun compress(data: ByteArray): ByteArray
fun hash(data: ByteArray): ByteArray

val compressed = compress(originalData)
val digest = hash(message)
```

**Java**

```java
static byte[] compress(byte[] data)
static byte[] hash(byte[] data)

byte[] compressed = compress(originalData);
byte[] digest = hash(message);
```

**C#**

```csharp
static byte[] Compress(byte[] data)
static byte[] Hash(byte[] data)

byte[] compressed = MyLib.Compress(originalData);
byte[] digest = MyLib.Hash(message);
```

**TypeScript**

```typescript
function compress(data: Uint8Array): Uint8Array
function hash(data: Uint8Array): Uint8Array

const compressed = compress(originalData)
const digest = hash(message)
```

**Python**

```python
def compress(data: bytes) -> bytes: ...
def hash(data: bytes) -> bytes: ...

compressed = compress(original_data)
digest = hash(message)
```

---

Source: https://boltffi.dev/docs/records

# Records

Records are value types: structs and enums marked with `#[data]`. When a record crosses the boundary, it gets copied. The target language receives its own copy of the data.

Classes are different. A class lives in Rust, and the target language holds a reference to it. See [Classes](/docs/classes.md) for when to use one over the other. Records may also define [associated constants](/docs/constants.md#associated-constants).

## Structs

Mark a struct with `#[data]` and it becomes a value type that can cross the FFI boundary. The caller creates an instance, passes it in, and BoltFFI handles the transfer.

The cost of passing a struct depends entirely on its contents. A struct with only primitive fields is nearly as fast as passing primitives directly. The bytes pack together and move across the boundary with no per-field overhead. This is the zero-copy path that makes BoltFFI fast.

A struct containing strings or collections takes longer to move across the boundary. Each string means an allocation on the receiving side. Each collection means transferring its length and all elements. Still fast in absolute terms, but noticeably slower than primitive-only structs in tight loops.

### Primitive-only structs

The bytes pack together and move across the boundary with no per-field overhead. This is the fast path.

**Rust**

```rust
#[data]
pub struct Point {
  pub x: f64,
  pub y: f64,
}

#[data]
pub struct Rect {
  pub x: f64,
  pub y: f64,
  pub width: f64,
  pub height: f64,
}

#[data(impl)]
impl Rect {
  pub fn contains(&self, point: Point) -> bool {
      point.x >= self.x
          && point.x <= self.x + self.width
          && point.y >= self.y
          && point.y <= self.y + self.height
  }
}
```

**Swift**

```swift
public struct Point {
  public var x: Double
  public var y: Double
}

public struct Rect {
  public var x: Double
  public var y: Double
  public var width: Double
  public var height: Double
}

extension Rect {
  public func contains(point: Point) -> Bool
}

let r = Rect(x: 0, y: 0, width: 100, height: 100)
let p = Point(x: 50, y: 50)
let inside = r.contains(point: p)
```

**Kotlin**

```kotlin
data class Point(
  val x: Double,
  val y: Double
)

data class Rect(
  val x: Double,
  val y: Double,
  val width: Double,
  val height: Double
) {
  fun contains(point: Point): Boolean
}

val r = Rect(0.0, 0.0, 100.0, 100.0)
val p = Point(50.0, 50.0)
val inside = r.contains(p)
```

**Java**

```java
// Java 16+
public record Point(double x, double y) {}
public record Rect(
  double x, double y,
  double width, double height) {
  public boolean contains(Point point)
}

// Java 8+
public final class Rect {
  public final double x;
  public final double y;
  public final double width;
  public final double height;

  public boolean contains(Point point)
}

Rect r = new Rect(0.0, 0.0, 100.0, 100.0);
Point p = new Point(50.0, 50.0);
boolean inside = r.contains(p);
```

**C#**

```csharp
public readonly record struct Point(
  double X,
  double Y
);

public readonly record struct Rect(
  double X,
  double Y,
  double Width,
  double Height
)
{
  public bool Contains(Point point)
}

Rect r = new Rect(0.0, 0.0, 100.0, 100.0);
Point p = new Point(50.0, 50.0);
bool inside = r.Contains(p);
```

**TypeScript**

```typescript
interface Point {
  readonly x: number
  readonly y: number
}

interface Rect {
  readonly x: number
  readonly y: number
  readonly width: number
  readonly height: number
}

const Rect = {
  contains(self: Rect, point: Point): boolean
}

const r: Rect = { x: 0, y: 0, width: 100, height: 100 }
const p: Point = { x: 50, y: 50 }
const inside = Rect.contains(r, p)
```

**Python**

```python
from dataclasses import dataclass

@dataclass
class Point:
  x: float
  y: float

@dataclass
class Rect:
  x: float
  y: float
  width: float
  height: float

  def contains(self, point: Point) -> bool: ...

r = Rect(0, 0, 100, 100)
p = Point(50, 50)
inside = r.contains(p)
```

### Structs with strings or collections

These take longer to cross the boundary. A struct with two strings means two allocations on the receiving side. Still fast for normal use, but measure if you're calling thousands of times per second.

**Rust**

```rust
#[data]
pub struct User {
  pub id: u64,
  pub name: String,
  pub email: String,
}

#[data]
pub struct SearchResult {
  pub query: String,
  pub matches: Vec<User>,
  pub total_count: u64,
}

#[export]
pub fn search_users(
  query: &str
) -> SearchResult {
  // ... search logic
  SearchResult {
      query: query.to_string(),
      matches: vec![],
      total_count: 0,
  }
}
```

**Swift**

```swift
public struct User {
  public var id: UInt64
  public var name: String
  public var email: String
}

public struct SearchResult {
  public var query: String
  public var matches: [User]
  public var totalCount: UInt64
}

func searchUsers(query: String) -> SearchResult

let result = searchUsers(query: "alice")
```

**Kotlin**

```kotlin
data class User(
  val id: ULong,
  val name: String,
  val email: String
)

data class SearchResult(
  val query: String,
  val matches: List<User>,
  val totalCount: ULong
)

fun searchUsers(query: String): SearchResult

val result = searchUsers("alice")
```

**Java**

```java
// Java 16+
public record User(
  long id, String name, String email) {}
public record SearchResult(
  String query,
  java.util.List<User> matches,
  long totalCount) {}

// Java 8+
public final class User {
  public final long id;
  public final String name;
  public final String email;
}
public final class SearchResult {
  public final String query;
  public final java.util.List<User> matches;
  public final long totalCount;
}

static SearchResult searchUsers(String query)

SearchResult result = searchUsers("alice");
```

**C#**

```csharp
public readonly record struct User(
  ulong Id,
  string Name,
  string Email
);

public readonly record struct SearchResult(
  string Query,
  User[] Matches,
  ulong TotalCount
);

static SearchResult SearchUsers(string query)

SearchResult result =
  MyLib.SearchUsers("alice");
```

**TypeScript**

```typescript
interface User {
  readonly id: bigint
  readonly name: string
  readonly email: string
}

interface SearchResult {
  readonly query: string
  readonly matches: User[]
  readonly totalCount: bigint
}

function searchUsers(query: string): SearchResult

const result = searchUsers("alice")
```

**Python**

```python
from dataclasses import dataclass

@dataclass
class User:
  id: int
  name: str
  email: str

@dataclass
class SearchResult:
  query: str
  matches: list[User]
  total_count: int

def search_users(query: str) -> SearchResult: ...

result = search_users("alice")
```

### Nested structs

A struct can contain other structs, and BoltFFI handles them recursively. There's no depth limit imposed by BoltFFI, though deeply nested structures take more time to move across the boundary.

**Rust**

```rust
#[data]
pub struct Address {
  pub street: String,
  pub city: String,
  pub zip: String,
}

#[data]
pub struct Company {
  pub name: String,
  pub address: Address,
  pub employee_count: u32,
}

#[export]
pub fn company_summary(c: Company) -> String {
  format!(
      "{} in {}, {} employees",
      c.name, c.address.city, c.employee_count
  )
}
```

**Swift**

```swift
public struct Address {
  public var street: String
  public var city: String
  public var zip: String
}

public struct Company {
  public var name: String
  public var address: Address
  public var employeeCount: UInt32
}

func companySummary(c: Company) -> String

let addr = Address(
  street: "123 Main",
  city: "Seattle",
  zip: "98101"
)
let co = Company(
  name: "Acme",
  address: addr,
  employeeCount: 50
)
let summary = companySummary(c: co)
```

**Kotlin**

```kotlin
data class Address(
  val street: String,
  val city: String,
  val zip: String
)

data class Company(
  val name: String,
  val address: Address,
  val employeeCount: UInt
)

fun companySummary(c: Company): String

val addr = Address(
  "123 Main",
  "Seattle",
  "98101"
)
val co = Company("Acme", addr, 50u)
val summary = companySummary(co)
```

**Java**

```java
// Java 16+
public record Address(
  String street, String city,
  String zip) {}
public record Company(
  String name, Address address,
  int employeeCount) {}

// Java 8+
public final class Address {
  public final String street;
  public final String city;
  public final String zip;
}
public final class Company {
  public final String name;
  public final Address address;
  public final int employeeCount;
}

static String companySummary(Company c)

Address addr = new Address(
  "123 Main", "Seattle", "98101");
Company co = new Company("Acme", addr, 50);
String summary = companySummary(co);
```

**C#**

```csharp
public readonly record struct Address(
  string Street,
  string City,
  string Zip
);

public readonly record struct Company(
  string Name,
  Address Address,
  uint EmployeeCount
);

static string CompanySummary(Company c)

Address addr = new Address(
  "123 Main", "Seattle", "98101");
Company co = new Company("Acme", addr, 50);
string summary = MyLib.CompanySummary(co);
```

**TypeScript**

```typescript
interface Address {
  readonly street: string
  readonly city: string
  readonly zip: string
}

interface Company {
  readonly name: string
  readonly address: Address
  readonly employeeCount: number
}

function companySummary(c: Company): string

const addr: Address = {
  street: "123 Main",
  city: "Seattle",
  zip: "98101"
}
const co: Company = { name: "Acme", address: addr, employeeCount: 50 }
const summary = companySummary(co)
```

**Python**

```python
from dataclasses import dataclass

@dataclass
class Address:
  street: str
  city: str
  zip: str

@dataclass
class Company:
  name: str
  address: Address
  employee_count: int

def company_summary(c: Company) -> str: ...

addr = Address("123 Main", "Seattle", "98101")
co = Company("Acme", addr, 50)
summary = company_summary(co)
```

### Optional fields

`Option<T>` in Rust becomes a nullable type in the target language. `None` becomes `null` or `nil` on the other side.

**Rust**

```rust
#[data]
pub struct Profile {
  pub username: String,
  pub display_name: Option<String>,
  pub bio: Option<String>,
  pub follower_count: u64,
}

#[export]
pub fn display_label(p: Profile) -> String {
  p.display_name
      .unwrap_or(p.username)
}
```

**Swift**

```swift
public struct Profile {
  public var username: String
  public var displayName: String?
  public var bio: String?
  public var followerCount: UInt64
}

func displayLabel(p: Profile) -> String

let p = Profile(
  username: "alice",
  displayName: "Alice Smith",
  bio: nil,
  followerCount: 1000
)
let label = displayLabel(p: p)
```

**Kotlin**

```kotlin
data class Profile(
  val username: String,
  val displayName: String?,
  val bio: String?,
  val followerCount: ULong
)

fun displayLabel(p: Profile): String

val p = Profile(
  "alice",
  "Alice Smith",
  null,
  1000uL
)
val label = displayLabel(p)
```

**Java**

```java
// Java 16+
public record Profile(
  String username,
  java.util.Optional<String> displayName,
  java.util.Optional<String> bio,
  long followerCount) {}

// Java 8+
public final class Profile {
  public final String username;
  public final java.util.Optional<String> displayName;
  public final java.util.Optional<String> bio;
  public final long followerCount;
}

static String displayLabel(Profile p)

Profile p = new Profile(
  "alice",
  java.util.Optional.of("Alice Smith"),
  java.util.Optional.empty(),
  1000L);
String label = displayLabel(p);
```

**C#**

```csharp
public readonly record struct Profile(
  string Username,
  string? DisplayName,
  string? Bio,
  ulong FollowerCount
);

static string DisplayLabel(Profile p)

Profile p = new Profile(
  "alice",
  "Alice Smith",
  null,
  1000);
string label = MyLib.DisplayLabel(p);
```

**TypeScript**

```typescript
interface Profile {
  readonly username: string
  readonly displayName: string | null
  readonly bio: string | null
  readonly followerCount: bigint
}

function displayLabel(p: Profile): string

const p: Profile = {
  username: "alice",
  displayName: "Alice Smith",
  bio: null,
  followerCount: 1000n
}
const label = displayLabel(p)
```

**Python**

```python
from dataclasses import dataclass

@dataclass
class Profile:
  username: str
  display_name: str | None
  bio: str | None
  follower_count: int

def display_label(p: Profile) -> str: ...

p = Profile(
  "alice",
  "Alice Smith",
  None,
  1000
)
label = display_label(p)
```

## Default values

Use `#[boltffi::default(...)]` on a field to give it a default value. The generated constructor will have that parameter as optional.

**Rust**

```rust
#[data]
pub struct Config {
  pub name: String,
  #[boltffi::default(3)]
  pub retries: i32,
  #[boltffi::default(true)]
  pub enabled: bool,
  #[boltffi::default("localhost")]
  pub host: String,
}
```

**Swift**

```swift
public struct Config {
  public var name: String
  public var retries: Int32
  public var enabled: Bool
  public var host: String
  
  public init(
      name: String,
      retries: Int32 = 3,
      enabled: Bool = true,
      host: String = "localhost"
  )
}

let cfg = Config(name: "myapp")
```

**Kotlin**

```kotlin
data class Config(
  val name: String,
  val retries: Int = 3,
  val enabled: Boolean = true,
  val host: String = "localhost"
)

val cfg = Config(name = "myapp")
```

**Java**

```java
// Java 16+
public record Config(
  String name, int retries,
  boolean enabled, String host) {}

// Java 8+
public final class Config {
  public final String name;
  public final int retries;
  public final boolean enabled;
  public final String host;
}

// Defaults: retries=3, enabled=true,
// host="localhost"
Config cfg = new Config(
  "myapp", 3, true, "localhost");
```

**C#**

```csharp
public readonly record struct Config(
  string Name,
  int Retries = 3,
  bool Enabled = true,
  string Host = "localhost"
);

Config cfg = new Config("myapp");
```

**TypeScript**

```typescript
interface Config {
  readonly name: string
  readonly retries: number      // default: 3
  readonly enabled: boolean     // default: true
  readonly host: string         // default: "localhost"
}

const cfg: Config = { name: "myapp", retries: 3, enabled: true, host: "localhost" }
```

**Python**

```python
from dataclasses import dataclass

@dataclass(frozen=True, slots=True)
class Config:
  name: str
  retries: int = 3
  enabled: bool = True
  host: str = "localhost"

cfg = Config("myapp")
```

### Supported default values

| Value         | Example                                |
| ------------- | -------------------------------------- |
| Booleans      | `#[boltffi::default(true)]`            |
| Integers      | `#[boltffi::default(42)]`              |
| Floats        | `#[boltffi::default(2.5)]`             |
| Strings       | `#[boltffi::default("hello")]`         |
| Enum variants | `#[boltffi::default(Status::Pending)]` |
| None          | `#[boltffi::default(None)]`            |

Option fields automatically default to `nil`/`null` if no explicit default is provided.

## Enums

Enums in Rust are more powerful than enums in most languages. A Rust enum can have variants with no data, variants with different data types, or any combination. BoltFFI handles all of these cases, generating the appropriate type in each target language.

### Simple enums

Enums with no associated data become native enum types that you can switch on, compare, or pass around.

**Rust**

```rust
#[data]
pub enum Status {
  Pending,
  Active,
  Completed,
  Failed,
}

#[export]
pub fn status_label(s: Status) -> String {
  match s {
      Status::Pending => "pending",
      Status::Active => "active",
      Status::Completed => "done",
      Status::Failed => "failed",
  }.to_string()
}
```

**Swift**

```swift
public enum Status {
  case pending
  case active
  case completed
  case failed
}

func statusLabel(s: Status) -> String

let label = statusLabel(s: .active)
```

**Kotlin**

```kotlin
enum class Status {
  Pending,
  Active,
  Completed,
  Failed
}

fun statusLabel(s: Status): String

val label = statusLabel(Status.Active)
```

**Java**

```java
public enum Status {
  PENDING(0),
  ACTIVE(1),
  COMPLETED(2),
  FAILED(3);

  public final int value;
}

static String statusLabel(Status s)

String label = statusLabel(Status.ACTIVE);
```

**C#**

```csharp
public enum Status
{
  Pending,
  Active,
  Completed,
  Failed,
}

static string StatusLabel(Status s)

string label = MyLib.StatusLabel(Status.Active);
```

**TypeScript**

```typescript
enum Status {
  Pending = 0,
  Active = 1,
  Completed = 2,
  Failed = 3
}

function statusLabel(s: Status): string

const label = statusLabel(Status.Active)
```

**Python**

```python
from enum import IntEnum

class Status(IntEnum):
  PENDING = 0
  ACTIVE = 1
  COMPLETED = 2
  FAILED = 3

def status_label(s: Status) -> str: ...

label = status_label(Status.ACTIVE)
```

### Enums with associated data

Each variant can carry different data. A loading state might carry progress; an error state might carry a message and code.

**Rust**

```rust
#[data]
pub enum LoadState {
  Idle,
  Loading { progress: f64 },
  Loaded { data: String },
  Error { message: String, code: i32 },
}

#[export]
pub fn describe_state(s: LoadState) -> String {
  match s {
      LoadState::Idle => 
          "Not started".to_string(),
      LoadState::Loading { progress } => 
          format!("{}%", (progress * 100.0) as i32),
      LoadState::Loaded { data } => 
          format!("Done: {} bytes", data.len()),
      LoadState::Error { message, .. } => 
          format!("Failed: {}", message),
  }
}
```

**Swift**

```swift
public enum LoadState {
  case idle
  case loading(progress: Double)
  case loaded(data: String)
  case error(message: String, code: Int32)
}

func describeState(s: LoadState) -> String

let s = LoadState.loading(progress: 0.5)
let desc = describeState(s: s)
```

**Kotlin**

```kotlin
sealed class LoadState {
  object Idle : LoadState()
  data class Loading(
      val progress: Double
  ) : LoadState()
  data class Loaded(
      val data: String
  ) : LoadState()
  data class Error(
      val message: String,
      val code: Int
  ) : LoadState()
}

fun describeState(s: LoadState): String

val s = LoadState.Loading(0.5)
val desc = describeState(s)
```

**Java**

```java
// Java 17+
public sealed interface LoadState {
  record Idle() implements LoadState {}
  record Loading(double progress)
      implements LoadState {}
  record Loaded(String data)
      implements LoadState {}
  record Error(String message, int code)
      implements LoadState {}
}

// Java 8+
public abstract class LoadState {
  public static final class Idle
      extends LoadState {}
  public static final class Loading
      extends LoadState {
      public final double progress;
  }
  public static final class Loaded
      extends LoadState {
      public final String data;
  }
  public static final class Error
      extends LoadState {
      public final String message;
      public final int code;
  }
}

static String describeState(LoadState s)

LoadState s = new LoadState.Loading(0.5);
String desc = describeState(s);
```

**C#**

```csharp
public abstract record LoadState
{
  public sealed record Idle : LoadState;
  public sealed record Loading(
      double Progress) : LoadState;
  public sealed record Loaded(
      string Data) : LoadState;
  public sealed record Error(
      string Message, int Code) : LoadState;
}

static string DescribeState(LoadState s)

LoadState s = new LoadState.Loading(0.5);
string desc = MyLib.DescribeState(s);
```

**TypeScript**

```typescript
type LoadState =
  | { readonly tag: "Idle" }
  | { readonly tag: "Loading"; readonly progress: number }
  | { readonly tag: "Loaded"; readonly data: string }
  | { readonly tag: "Error"; readonly message: string; readonly code: number }

function describeState(s: LoadState): string

const s: LoadState = { tag: "Loading", progress: 0.5 }
const desc = describeState(s)
```

**Python**

```python
from dataclasses import dataclass

class LoadState: ...

class LoadStateIdle(LoadState): ...

@dataclass
class LoadStateLoading(LoadState):
  progress: float

@dataclass
class LoadStateLoaded(LoadState):
  data: str

@dataclass
class LoadStateError(LoadState):
  message: str
  code: int

def describe_state(s: LoadState) -> str: ...

state = LoadStateLoading(0.5)
desc = describe_state(state)
```

## Methods and constructors

Use `#[data(impl)]` on an `impl` block to expose constructors, instance methods, and static methods on records and enums. The generated bindings use each language's native idiom for value-type members.

### Constructors

Functions that return `Self` become constructors or factory methods. `fn new(...)` becomes the default constructor. Other names become named constructors. Return `Result<Self, E>` for fallible constructors, or `Option<Self>` for optional ones.

**Rust**

```rust
#[data]
pub struct Point {
  pub x: f64,
  pub y: f64,
}

#[data(impl)]
impl Point {
  pub fn new(x: f64, y: f64) -> Self {
      Point { x, y }
  }

  pub fn origin() -> Self {
      Point { x: 0.0, y: 0.0 }
  }

  pub fn from_polar(
      r: f64, theta: f64
  ) -> Self {
      Point {
          x: r * theta.cos(),
          y: r * theta.sin(),
      }
  }

  pub fn try_unit(
      x: f64, y: f64
  ) -> Result<Self, String> {
      let len = (x * x + y * y).sqrt();
      if len == 0.0 {
          Err("zero vector".to_string())
      } else {
          Ok(Point {
              x: x / len,
              y: y / len,
          })
      }
  }
}
```

**Swift**

```swift
public struct Point {
  public var x: Double
  public var y: Double
}

extension Point {
  // fn new -> default init
  public static func new(
      x: Double, y: Double
  ) -> Point

  // named constructor
  public static func origin() -> Point

  // named init
  public init(
      fromPolar r: Double,
      theta: Double
  )

  public init(
      tryUnit x: Double,
      y: Double
  ) throws
}

let p = Point.origin()
let q = Point(fromPolar: 2.0, theta: .pi / 4)
let u = try Point(tryUnit: 3.0, y: 4.0)
```

**Kotlin**

```kotlin
data class Point(
  val x: Double,
  val y: Double
) {
  companion object {
      // fn new -> constructor
      fun new(x: Double, y: Double): Point

      // named constructor
      fun origin(): Point

      // named factory
      fun fromPolar(
          r: Double, theta: Double
      ): Point

      @Throws(FfiException::class)
      fun tryUnit(
          x: Double, y: Double
      ): Point
  }
}

val p = Point.origin()
val q = Point.fromPolar(2.0, PI / 4)
val u = Point.tryUnit(3.0, 4.0)
```

**Java**

```java
public record Point(
  double x, double y) {

  public static Point origin()

  public static Point fromPolar(
      double r, double theta)

  public static Point tryUnit(
      double x, double y)
}

Point p = Point.origin();
Point q = Point.fromPolar(2.0, Math.PI / 4);
Point u = Point.tryUnit(3.0, 4.0);
```

**TypeScript**

```typescript
interface Point {
  readonly x: number
  readonly y: number
}

const Point = {
  new(x: number, y: number): Point,

  origin(): Point,

  fromPolar(
      r: number, theta: number
  ): Point,

  tryUnit(
      x: number, y: number
  ): Point,
}

const p = Point.origin()
const q = Point.fromPolar(2.0, Math.PI / 4)
const u = Point.tryUnit(3.0, 4.0)
```

**Python**

```python
from dataclasses import dataclass
import math

@dataclass
class Point:
  x: float
  y: float

  @classmethod
  def new(cls, x: float, y: float) -> "Point": ...
  @classmethod
  def origin(cls) -> "Point": ...
  @classmethod
  def from_polar(cls, r: float, theta: float) -> "Point": ...
  @classmethod
  def try_unit(cls, x: float, y: float) -> "Point": ...

p = Point.origin()
q = Point.from_polar(2.0, math.pi / 4)
u = Point.try_unit(3.0, 4.0)
```

### Instance methods

Functions that take `&self` become instance methods. Because records are value types, calling a method copies the entire value across the FFI boundary. For small structs like `Point` this is negligible, but keep it in mind for large records with many fields or nested collections.

**Rust**

```rust
#[data(impl)]
impl Point {
  pub fn distance(&self) -> f64 {
      (self.x * self.x
          + self.y * self.y)
          .sqrt()
  }

  pub fn add(
      &self, other: Point
  ) -> Point {
      Point {
          x: self.x + other.x,
          y: self.y + other.y,
      }
  }
}
```

**Swift**

```swift
extension Point {
  public func distance() -> Double

  public func add(
      other: Point
  ) -> Point
}

let p = Point(x: 3.0, y: 4.0)
let d = p.distance()       // 5.0
let sum = p.add(other: q)
```

**Kotlin**

```kotlin
data class Point(...) {
  fun distance(): Double

  fun add(other: Point): Point
}

val p = Point(3.0, 4.0)
val d = p.distance()       // 5.0
val sum = p.add(q)
```

**Java**

```java
public record Point(...) {
  public double distance()

  public Point add(Point other)
}

Point p = new Point(3.0, 4.0);
double d = p.distance();   // 5.0
Point sum = p.add(q);
```

**TypeScript**

```typescript
const Point = {
  distance(self: Point): number,

  add(self: Point, other: Point): Point,
}

const p: Point = { x: 3, y: 4 }
const d = Point.distance(p)       // 5.0
const sum = Point.add(p, q)
```

**Python**

```python
from dataclasses import dataclass

@dataclass
class Point:
  x: float
  y: float

  def distance(self) -> float: ...
  def add(self, other: "Point") -> "Point": ...

p = Point(3.0, 4.0)
d = p.distance()       # 5.0
sum_value = p.add(q)
```

### Mutating methods

Functions that take `&mut self` modify the value in place. In Swift this generates a `mutating` method that reassigns `self`. In Kotlin, Java, C#, and TypeScript the method returns a new copy with the updated values. The same value-copy cost from instance methods applies here since the record is copied both in and out.

**Rust**

```rust
#[data(impl)]
impl Point {
  pub fn scale(&mut self, factor: f64) {
      self.x *= factor;
      self.y *= factor;
  }
}
```

**Swift**

```swift
extension Point {
  public mutating func scale(
      factor: Double
  )
}

var p = Point(x: 1.0, y: 2.0)
p.scale(factor: 3.0)
// p is now (3.0, 6.0)
```

**Kotlin**

```kotlin
data class Point(...) {
  fun scale(factor: Double): Point
}

var p = Point(1.0, 2.0)
p = p.scale(3.0)
// p is now (3.0, 6.0)
```

**Java**

```java
public record Point(...) {
  public Point scale(double factor)
}

Point p = new Point(1.0, 2.0);
p = p.scale(3.0);
// p is now (3.0, 6.0)
```

**TypeScript**

```typescript
const Point = {
  scale(self: Point, factor: number): Point,
}

let p: Point = { x: 1, y: 2 }
p = Point.scale(p, 3.0)
// p is now { x: 3, y: 6 }
```

**Python**

```python
from dataclasses import dataclass

@dataclass
class Point:
  x: float
  y: float

  def scale(self, factor: float) -> "Point": ...

p = Point(1.0, 2.0)
p = p.scale(3.0)
# p is now Point(3.0, 6.0)
```

### Static methods

Functions that take no `self` parameter and don't return `Self` are exposed as static methods.

**Rust**

```rust
#[data(impl)]
impl Point {
  pub fn dimensions() -> u32 {
      2
  }
}
```

**Swift**

```swift
extension Point {
  public static func dimensions() -> UInt32
}

let dims = Point.dimensions() // 2
```

**Kotlin**

```kotlin
data class Point(...) {
  companion object {
      fun dimensions(): UInt
  }
}

val dims = Point.dimensions() // 2
```

**Java**

```java
public record Point(...) {
  public static int dimensions()
}

int dims = Point.dimensions(); // 2
```

**TypeScript**

```typescript
const Point = {
  dimensions(): number,
}

const dims = Point.dimensions() // 2
```

**Python**

```python
from dataclasses import dataclass

@dataclass
class Point:
  x: float
  y: float

  @staticmethod
  def dimensions() -> int: ...

dims = Point.dimensions()  # 2
```

### Enum methods

The same `#[data(impl)]` works on enums. Methods and constructors follow the same rules.

**Rust**

```rust
#[data]
pub enum Direction {
  North,
  East,
  South,
  West,
}

#[data(impl)]
impl Direction {
  pub fn opposite(&self) -> Direction {
      match self {
          Direction::North => Direction::South,
          Direction::South => Direction::North,
          Direction::East => Direction::West,
          Direction::West => Direction::East,
      }
  }

  pub fn from_degrees(deg: f64) -> Self {
      match ((deg % 360.0 + 360.0) % 360.0) as u32 {
          0..=89 => Direction::North,
          90..=179 => Direction::East,
          180..=269 => Direction::South,
          _ => Direction::West,
      }
  }
}
```

**Swift**

```swift
public enum Direction {
  case north, east, south, west
}

extension Direction {
  public func opposite() -> Direction
  public static func fromDegrees(
      _ deg: Double
  ) -> Direction
}

let d = Direction.north.opposite() // .south
let e = Direction.fromDegrees(90)  // .east
```

**Kotlin**

```kotlin
enum class Direction {
  North, East, South, West;

  fun opposite(): Direction

  companion object {
      fun fromDegrees(deg: Double): Direction
  }
}

val d = Direction.North.opposite() // South
val e = Direction.fromDegrees(90.0) // East
```

**Java**

```java
public enum Direction {
  NORTH(0), EAST(1), SOUTH(2), WEST(3);

  public Direction opposite()

  public static Direction fromDegrees(
      double deg)
}

Direction d = Direction.NORTH.opposite();
Direction e = Direction.fromDegrees(90.0);
```

**TypeScript**

```typescript
enum Direction {
  North = 0, East = 1,
  South = 2, West = 3
}

namespace Direction {
  export function opposite(
      self: Direction
  ): Direction

  export function fromDegrees(
      deg: number
  ): Direction
}

const d = Direction.opposite(Direction.North)
const e = Direction.fromDegrees(90)
```

**Python**

```python
from enum import IntEnum

class Direction(IntEnum):
  NORTH = 0
  EAST = 1
  SOUTH = 2
  WEST = 3

  def opposite(self) -> "Direction": ...

  @classmethod
  def from_degrees(cls, deg: float) -> "Direction": ...

d = Direction.NORTH.opposite()
e = Direction.from_degrees(90)
```

---

Source: https://boltffi.dev/docs/classes

# Classes

BoltFFI has two ways to expose a struct: as data or as a class.

Data (`#[data]`) is for plain values. A `Point { x, y }` or a `User { id, name, email }`. Data is copied when it crosses the boundary. The target language gets a struct or record with public fields. You can also attach methods, constructors, and [associated constants](/docs/constants.md#associated-constants) with `#[data(impl)]`. See [Records](/docs/records.md#methods-and-constructors).

Classes (`#[export] impl`) are for objects with behavior. A `DatabaseConnection` or a `HttpClient`. The object lives in Rust, the target language holds a reference to it. Methods operate on that reference. The object is not copied - there's one instance, and both sides point to it. The exported impl may also define [associated constants](/docs/constants.md#associated-constants).

Use data when you're passing values around. Use classes when you're managing state or resources.

## Defining a class

Put `#[export]` on the impl block, not the struct. The struct stays private; only the methods you define in the impl block are exposed.

**Rust**

```rust
use std::sync::Mutex;

pub struct Counter {
  value: Mutex<i32>,
}

#[export]
impl Counter {
  pub fn new() -> Self {
      Counter { value: Mutex::new(0) }
  }
  
  pub fn increment(&self) {
      *self.value.lock().unwrap() += 1;
  }
  
  pub fn get(&self) -> i32 {
      *self.value.lock().unwrap()
  }
}
```

**Swift**

```swift
public class Counter {
  public init()
  public func increment()
  public func get() -> Int32
}

let counter = Counter()
counter.increment()
counter.increment()
print(counter.get())  // 2
```

**Kotlin**

```kotlin
class Counter {
  constructor()
  fun increment()
  fun get(): Int
}

val counter = Counter()
counter.increment()
counter.increment()
println(counter.get())  // 2
```

**Java**

```java
public final class Counter
  implements AutoCloseable {
  public Counter()
  public void increment()
  public int get()
  public void close()
}

Counter counter = new Counter();
counter.increment();
counter.increment();
System.out.println(counter.get()); // 2
counter.close();
```

**C#**

```csharp
public sealed class Counter : IDisposable
{
  public Counter()
  public void Increment()
  public int Get()
  public void Dispose()
}

using Counter counter = new Counter();
counter.Increment();
counter.Increment();
Console.WriteLine(counter.Get()); // 2
```

**TypeScript**

```typescript
declare class Counter {
  private constructor()
  static new(): Counter
  increment(): void
  get(): number
  dispose(): void
}

const counter = Counter.new()
counter.increment()
counter.increment()
console.log(counter.get())  // 2
counter.dispose()
```

**Python**

```python
class Counter:
  def __init__(self) -> None: ...
  def increment(self) -> None: ...
  def get(self) -> int: ...

counter = Counter()
counter.increment()
counter.increment()
print(counter.get())  # 2
```

## Constructors

Methods that return `Self` become constructors. How they appear in the target language depends on the method name and parameters.

### The `new()` method

A method named `new()` becomes the primary constructor.

**Rust**

```rust
#[export]
impl Counter {
  pub fn new() -> Self {
      Counter { value: 0 }
  }
}
```

**Swift**

```swift
public class Counter {
  public init()
}

let c = Counter()
```

**Kotlin**

```kotlin
class Counter {
  constructor()
}

val c = Counter()
```

**Java**

```java
public final class Counter
  implements AutoCloseable {
  public Counter()
}

Counter c = new Counter();
```

**C#**

```csharp
public sealed class Counter : IDisposable
{
  public Counter()
}

using Counter c = new Counter();
```

**TypeScript**

```typescript
declare class Counter {
  private constructor()
  static new(): Counter
  dispose(): void
}

const c = Counter.new()
```

**Python**

```python
class Counter:
  def __init__(self) -> None: ...

c = Counter()
```

### Named constructors with parameters

Methods with parameters that return `Self` become additional constructors. In Swift, they become `convenience init`. In Kotlin, they go in the companion object. Java and C# expose them as static factory methods on the generated class.

**Rust**

```rust
#[export]
impl Database {
  pub fn new() -> Self {
      Database { path: ":memory:".into() }
  }
  
  pub fn open(path: &str) -> Self {
      Database { path: path.into() }
  }
  
  pub fn with_options(
      path: &str,
      read_only: bool
  ) -> Self {
      Database {
          path: path.into(),
          read_only,
      }
  }
}
```

**Swift**

```swift
public class Database {
  public init()
  public convenience init(path: String)
  public convenience init(path: String, readOnly: Bool)
}

let db1 = Database()
let db2 = Database(path: "data.db")
let db3 = Database(path: "data.db", readOnly: true)
```

**Kotlin**

```kotlin
class Database {
  constructor()

  companion object {
      fun open(path: String): Database
      fun withOptions(path: String, readOnly: Boolean): Database
  }
}

val db1 = Database()
val db2 = Database.open("data.db")
val db3 = Database.withOptions("data.db", true)
```

**Java**

```java
public final class Database
  implements AutoCloseable {
  public Database()
  public static Database open(String path)
  public static Database withOptions(
      String path, boolean readOnly)
}

Database db1 = new Database();
Database db2 = Database.open("data.db");
Database db3 = Database.withOptions(
  "data.db", true);
```

**C#**

```csharp
public sealed class Database : IDisposable
{
  public Database()
  public static Database Open(string path)
  public static Database WithOptions(
      string path, bool readOnly)
}

using Database db1 = new Database();
using Database db2 = Database.Open("data.db");
using Database db3 = Database.WithOptions(
  "data.db", true);
```

**TypeScript**

```typescript
declare class Database {
  private constructor()
  static new(): Database
  static open(path: string): Database
  static withOptions(path: string, readOnly: boolean): Database
  dispose(): void
}

const db1 = Database.new()
const db2 = Database.open("data.db")
const db3 = Database.withOptions("data.db", true)
```

**Python**

```python
class Database:
  def __init__(self) -> None: ...

  @classmethod
  def open(cls, path: str) -> "Database": ...

  @classmethod
  def with_options(
      cls,
      path: str,
      read_only: bool
  ) -> "Database": ...

db1 = Database()
db2 = Database.open("data.db")
db3 = Database.with_options("data.db", True)
```

### Factory methods (no parameters)

Methods with no parameters and a name other than `new` become factory methods.

**Rust**

```rust
#[export]
impl Config {
  pub fn new() -> Self {
      Config::default_config()
  }
  
  pub fn production() -> Self {
      Config { debug: false, timeout: 30 }
  }
  
  pub fn development() -> Self {
      Config { debug: true, timeout: 120 }
  }
}
```

**Swift**

```swift
public class Config {
  public init()
  public static func production() -> Config
  public static func development() -> Config
}

let cfg = Config.production()
```

**Kotlin**

```kotlin
class Config {
  constructor()

  companion object {
      fun production(): Config
      fun development(): Config
  }
}

val cfg = Config.production()
```

**Java**

```java
public final class Config
  implements AutoCloseable {
  public Config()
  public static Config production()
  public static Config development()
}

Config cfg = Config.production();
```

**C#**

```csharp
public sealed class Config : IDisposable
{
  public Config()
  public static Config Production()
  public static Config Development()
}

using Config cfg = Config.Production();
```

**TypeScript**

```typescript
declare class Config {
  private constructor()
  static new(): Config
  static production(): Config
  static development(): Config
  dispose(): void
}

const cfg = Config.production()
```

**Python**

```python
class Config:
  def __init__(self) -> None: ...

  @classmethod
  def production(cls) -> "Config": ...

  @classmethod
  def development(cls) -> "Config": ...

cfg = Config.production()
```

### Fallible constructors

Constructors can return `Result<Self, E>`. The error type must be marked with `#[error]`. The constructor becomes throwing in the target language.

**Rust**

```rust
#[error]
pub enum DbError {
  NotFound,
  PermissionDenied,
}

#[export]
impl Database {
  pub fn open(path: &str) -> Result<Self, DbError> {
      if !path_exists(path) {
          return Err(DbError::NotFound);
      }
      Ok(Database { path: path.into() })
  }
}
```

**Swift**

```swift
public class Database {
  public convenience init(path: String) throws
}

do {
  let db = try Database(path: "data.db")
} catch DbError.notFound {
  print("file not found")
}
```

**Kotlin**

```kotlin
class Database {
  companion object {
      @Throws(DbError::class)
      fun open(path: String): Database
  }
}

try {
  val db = Database.open("data.db")
} catch (e: DbError.NotFound) {
  println("file not found")
}
```

**Java**

```java
public final class Database
  implements AutoCloseable {
  public static Database open(String path)
      // throws DbError.Exception
}

try {
  Database db = Database.open("data.db");
} catch (DbError.Exception e) {
  if (e.getError() == DbError.NOT_FOUND)
      System.out.println("file not found");
}
```

**C#**

```csharp
public sealed class Database : IDisposable
{
  public static Database Open(string path)
}

try {
  using Database db = Database.Open("data.db");
} catch (DbErrorException e) {
  if (e.Error == DbError.NotFound)
      Console.WriteLine("file not found");
}
```

**TypeScript**

```typescript
declare enum DbError {
  NotFound = 0,
  PermissionDenied = 1
}

declare class DbErrorException extends Error {
  readonly code: DbError
}

declare class Database {
  private constructor()
  static open(path: string): Database  // throws
  dispose(): void
}

try {
  const db = Database.open("data.db")
} catch (e) {
  if (e instanceof DbErrorException && e.code === DbError.NotFound) {
      console.log("file not found")
  }
}
```

**Python**

```python
from enum import IntEnum

class DbError(IntEnum):
  NOT_FOUND = 0
  PERMISSION_DENIED = 1

class DbErrorException(RuntimeError):
  error: DbError

class Database:
  @classmethod
  def open(cls, path: str) -> "Database": ...

try:
  db = Database.open("data.db")
except DbErrorException as error:
  if error.error == DbError.NOT_FOUND:
      print("file not found")
```

## Methods

Once you have a class, you define methods on it. Any `pub fn` in the `#[export] impl` block that takes `&self` becomes an instance method in the target language. The target language calls the method, BoltFFI routes it to Rust, and Rust executes it on the actual object.

Because the target language can call your methods from any thread at any time, BoltFFI requires `&self` (shared reference), not `&mut self` (exclusive reference). If your method needs to change state, use interior mutability (`Mutex`, `RwLock`, atomics, or whatever synchronization fits your use case). This way multiple threads can safely call methods on the same object without data races. The [`&self vs &mut self`](#self-vs-mut-self) section below covers this in detail.

**Rust**

```rust
use std::sync::Mutex;

pub struct Account {
  balance: Mutex<i64>,
}

#[export]
impl Account {
  pub fn balance(&self) -> i64 {
      *self.balance.lock().unwrap()
  }
  
  pub fn deposit(&self, amount: i64) {
      *self.balance.lock().unwrap() += amount;
  }
  
  pub fn withdraw(
      &self,
      amount: i64
  ) -> Result<(), AccountError> {
      let mut balance = self.balance.lock().unwrap();
      if amount > *balance {
          return Err(AccountError::Insufficient);
      }
      *balance -= amount;
      Ok(())
  }
}
```

**Swift**

```swift
public class Account {
  public func balance() -> Int64
  public func deposit(amount: Int64)
  public func withdraw(amount: Int64) throws
}

let acc = Account()
acc.deposit(amount: 100)
print(acc.balance())

do {
  try acc.withdraw(amount: 50)
} catch {
  print(error)
}
```

**Kotlin**

```kotlin
class Account {
  fun balance(): Long
  fun deposit(amount: Long)
  @Throws(AccountError::class)
  fun withdraw(amount: Long)
}

val acc = Account()
acc.deposit(100)
println(acc.balance())

try {
  acc.withdraw(50)
} catch (e: AccountError) {
  println(e.message)
}
```

**Java**

```java
public final class Account
  implements AutoCloseable {
  public long balance()
  public void deposit(long amount)
  public void withdraw(long amount)
      // throws AccountError.Exception
}

Account acc = new Account();
acc.deposit(100L);
System.out.println(acc.balance());

try {
  acc.withdraw(50L);
} catch (AccountError.Exception e) {
  System.out.println(e.getError());
}
```

**C#**

```csharp
public sealed class Account : IDisposable
{
  public long Balance()
  public void Deposit(long amount)
  public void Withdraw(long amount)
}

using Account acc = new Account();
acc.Deposit(100);
Console.WriteLine(acc.Balance());

try {
  acc.Withdraw(50);
} catch (AccountErrorException e) {
  Console.WriteLine(e.Error);
}
```

**TypeScript**

```typescript
declare class Account {
  private constructor()
  static new(): Account
  balance(): bigint
  deposit(amount: bigint): void
  withdraw(amount: bigint): void  // throws
  dispose(): void
}

const acc = Account.new()
acc.deposit(100n)
console.log(acc.balance())

try {
  acc.withdraw(50n)
} catch (e) {
  console.log(e)
}
```

**Python**

```python
from enum import IntEnum

class Account:
  def __init__(self) -> None: ...
  def balance(self) -> int: ...
  def deposit(self, amount: int) -> None: ...
  def withdraw(self, amount: int) -> None: ...

class AccountError(IntEnum):
  INSUFFICIENT_FUNDS = 0

class AccountErrorException(RuntimeError):
  error: AccountError

acc = Account()
acc.deposit(100)
print(acc.balance())

try:
  acc.withdraw(50)
except AccountErrorException as error:
  print(error)
```

### \&self vs \&mut self

When the target language calls a method, there's no guarantee which thread it comes from. Swift might dispatch from the main thread, a GCD queue, or a `Task`. Kotlin might call from a coroutine on any dispatcher. Java might call from any thread in a thread pool. C# might call from the thread pool or any `Task` continuation. You don't control this.

That's the problem with `&mut self`. It requires exclusive access to the object. If two threads call a `&mut self` method at the same time, you get undefined behavior. BoltFFI catches this at compile time:

```rust
#[export]
impl Counter {
    pub fn increment(&mut self) {  // Compile error
        self.value += 1;
    }
}
```

```
error: BoltFFI: `&mut self` methods are not thread-safe in FFI contexts

Two threads calling `&mut self` on the same instance = undefined behavior.

Options:
1. Use `&self` with interior mutability (Mutex, RefCell, etc.) [recommended]
2. Add #[export(single_threaded)] ONLY if you enforce thread safety in the target
   language and want to avoid synchronization overhead you don't need
```

Use `&self` instead, and move the synchronization inside your struct. `Mutex`, `RwLock`, atomics, channels, or any other mechanism that makes concurrent access safe:

```rust
use std::sync::Mutex;

pub struct Counter {
    value: Mutex<i32>,
}

#[export]
impl Counter {
    pub fn new() -> Self {
        Counter { value: Mutex::new(0) }
    }
    
    pub fn increment(&self) {
        *self.value.lock().unwrap() += 1;
    }
    
    pub fn get(&self) -> i32 {
        *self.value.lock().unwrap()
    }
}
```

If you know the object will only ever be accessed from a single thread and want to skip the synchronization overhead, see [Single-threaded mode](#single-threaded-mode).

### Static methods

Methods without `self` become static methods on the class.

**Rust**

```rust
#[export]
impl Config {
  pub fn default_timeout() -> u32 {
      30
  }
  
  pub fn max_connections() -> u32 {
      100
  }
}
```

**Swift**

```swift
public class Config {
  public static func defaultTimeout() -> UInt32
  public static func maxConnections() -> UInt32
}

let t = Config.defaultTimeout()
```

**Kotlin**

```kotlin
class Config {
  companion object {
      fun defaultTimeout(): UInt
      fun maxConnections(): UInt
  }
}

val t = Config.defaultTimeout()
```

**Java**

```java
public final class Config
  implements AutoCloseable {
  public static int defaultTimeout()
  public static int maxConnections()
}

int t = Config.defaultTimeout();
```

**C#**

```csharp
public sealed class Config : IDisposable
{
  public static uint DefaultTimeout()
  public static uint MaxConnections()
}

uint t = Config.DefaultTimeout();
```

**TypeScript**

```typescript
declare class Config {
  private constructor()
  static defaultTimeout(): number
  static maxConnections(): number
}

const t = Config.defaultTimeout()
```

**Python**

```python
class Config:
  @staticmethod
  def default_timeout() -> int: ...

  @staticmethod
  def max_connections() -> int: ...

t = Config.default_timeout()
```

### Async methods

Mark a method `async` and it becomes an async method in the target language. BoltFFI has no built-in executor. You choose your Rust async runtime (Tokio, async-std, etc.), and the target language's async system coordinates with it automatically. See [Async](/docs/async.md) for more.

**Rust**

```rust
#[export]
impl HttpClient {
  pub fn new() -> Self {
      HttpClient { client: reqwest::Client::new() }
  }
  
  pub async fn get(&self, url: &str) -> Result<String, HttpError> {
      let resp = self.client.get(url).send().await?;
      let body = resp.text().await?;
      Ok(body)
  }
  
  pub async fn post(
      &self,
      url: &str,
      body: &str
  ) -> Result<String, HttpError> {
      let resp = self.client
          .post(url)
          .body(body.to_string())
          .send()
          .await?;
      Ok(resp.text().await?)
  }
}
```

**Swift**

```swift
public class HttpClient {
  public init()
  public func get(url: String) async throws -> String
  public func post(url: String, body: String) async throws -> String
}

let client = HttpClient()
let data = try await client.get(url: "https://api.example.com")
```

**Kotlin**

```kotlin
class HttpClient {
  constructor()
  suspend fun get(url: String): String
  suspend fun post(url: String, body: String): String
}

val client = HttpClient()
val data = client.get("https://api.example.com")
```

**Java**

```java
public final class HttpClient
  implements AutoCloseable {
  public HttpClient()

  // Java 21+ (virtual threads)
  public String get(String url)
      // throws HttpErrorException
  public String post(String url, String body)
      // throws HttpErrorException

  // Java 8+ (CompletableFuture)
  public CompletableFuture<String> get(
      String url)
  public CompletableFuture<String> post(
      String url, String body)
}

HttpClient client = new HttpClient();
String data = client.get(
  "https://api.example.com");
```

**C#**

```csharp
public sealed class HttpClient : IDisposable
{
  public HttpClient()
  public Task<string> Get(string url)
  public Task<string> Post(string url, string body)
}

using HttpClient client = new HttpClient();
string data = await client.Get(
  "https://api.example.com");
```

**TypeScript**

```typescript
declare class HttpClient {
  private constructor()
  static new(): HttpClient
  async get(url: string): Promise<string>
  async post(url: string, body: string): Promise<string>
  dispose(): void
}

const client = HttpClient.new()
const data = await client.get("https://api.example.com")
```

**Python**

```python
class HttpClient:
  def __init__(self) -> None: ...
  async def get(self, url: str) -> str: ...
  async def post(self, url: str, body: str) -> str: ...

client = HttpClient()
data = await client.get("https://api.example.com")
```

### Methods that take or return classes

Methods can accept or return other class instances.

**Rust**

```rust
#[export]
impl Session {
  pub fn new(user: &User) -> Self {
      Session { user_id: user.id() }
  }
  
  pub fn user(&self) -> User {
      User::find(self.user_id)
  }
}

#[export]
impl User {
  pub fn id(&self) -> u64 {
      self.id
  }
}
```

**Swift**

```swift
public class Session {
  public init(user: User)
  public func user() -> User
}

let user = User(name: "alice")
let session = Session(user: user)
let u = session.user()
```

**Kotlin**

```kotlin
class Session {
  constructor(user: User)
  fun user(): User
}

val user = User("alice")
val session = Session(user)
val u = session.user()
```

**Java**

```java
public final class Session
  implements AutoCloseable {
  public Session(User user)
  public User user()
}

User user = new User("alice");
Session session = new Session(user);
User u = session.user();
```

**C#**

```csharp
public sealed class Session : IDisposable
{
  public Session(User user)
  public User User()
}

using User user = new User("alice");
using Session session = new Session(user);
User u = session.User();
```

**TypeScript**

```typescript
declare class Session {
  private constructor()
  static new(user: User): Session
  user(): User
  dispose(): void
}

declare class User {
  private constructor()
  id(): bigint
}

const user = User.new("alice")
const session = Session.new(user)
const u = session.user()
```

**Python**

```python
class Session:
  def __init__(self, user: User) -> None: ...
  def user(self) -> User: ...

class User:
  def __init__(self, name: str) -> None: ...
  def id(self) -> int: ...

user = User("alice")
session = Session(user)
u = session.user()
```

## Skipping methods

Use `#[skip]` to exclude a method from FFI export. The method stays in Rust but isn't exposed to the target language. The skipped method is still callable from Rust, just not from the target language.

**Rust**

```rust
#[export]
impl MyClass {
  pub fn exported(&self) -> i32 {
      self.helper() * 2
  }
  
  #[skip]
  pub fn helper(&self) -> i32 {
      42
  }
}
```

**Swift**

```swift
public class MyClass {
  public func exported() -> Int32
  // helper() is not exposed
}

let obj = MyClass()
print(obj.exported()) // 84
```

**Kotlin**

```kotlin
class MyClass {
  fun exported(): Int
  // helper() is not exposed
}

val obj = MyClass()
println(obj.exported()) // 84
```

**Java**

```java
public final class MyClass
  implements AutoCloseable {
  public int exported()
  // helper() is not exposed
}

MyClass obj = new MyClass();
System.out.println(obj.exported()); // 84
```

**C#**

```csharp
public sealed class MyClass : IDisposable
{
  public int Exported()
  // Helper() is not exposed
}

using MyClass obj = new MyClass();
Console.WriteLine(obj.Exported()); // 84
```

**TypeScript**

```typescript
declare class MyClass {
  private constructor()
  exported(): number
  // helper() is not exposed
  dispose(): void
}

const obj = MyClass.new()
console.log(obj.exported()) // 84
```

**Python**

```python
class MyClass:
  def exported(self) -> int: ...

obj = MyClass()
print(obj.exported())  # 84
```

## Thread safety

BoltFFI requires exported classes to be `Send + Sync` by default. This is a compile-time check. If your struct isn't thread-safe, compilation fails.

If your struct contains types that aren't thread-safe (like `RefCell`, `Rc`, or raw pointers), you have two options:

1. Make it thread-safe using synchronization primitives like `Mutex`, `RwLock`, or atomics. For shared ownership across threads, combine with `Arc` (e.g., `Arc<Mutex<T>>`).

2. Add `#[export(single_threaded)]`. This disables the `Send + Sync` check, but you're responsible for ensuring the class is only used from a single thread.

**Rust**

```rust
use std::sync::atomic::{AtomicI32, Ordering};

pub struct SafeCounter {
  value: AtomicI32,
}

#[export]
impl SafeCounter {
  pub fn new() -> Self {
      SafeCounter {
          value: AtomicI32::new(0)
      }
  }
  
  pub fn increment(&self) {
      self.value.fetch_add(1, Ordering::SeqCst);
  }
  
  pub fn get(&self) -> i32 {
      self.value.load(Ordering::SeqCst)
  }
}
```

**Swift**

```swift
public class SafeCounter {
  public init()
  public func increment()
  public func get() -> Int32
}

let counter = SafeCounter()
DispatchQueue.concurrentPerform(iterations: 100) { _ in
  counter.increment()
}
```

**Kotlin**

```kotlin
class SafeCounter {
  constructor()
  fun increment()
  fun get(): Int
}

val counter = SafeCounter()
repeat(100) {
  thread { counter.increment() }
}
```

**Java**

```java
public final class SafeCounter
  implements AutoCloseable {
  public SafeCounter()
  public void increment()
  public int get()
}

SafeCounter counter = new SafeCounter();
for (int i = 0; i < 100; i++) {
  new Thread(counter::increment).start();
}
```

**C#**

```csharp
public sealed class SafeCounter : IDisposable
{
  public SafeCounter()
  public void Increment()
  public int Get()
}

using SafeCounter counter = new SafeCounter();
Parallel.For(0, 100, _ => counter.Increment());
```

**TypeScript**

```typescript
declare class SafeCounter {
  private constructor()
  static new(): SafeCounter
  increment(): void
  get(): number
  dispose(): void
}

const counter = SafeCounter.new()
await Promise.all(
  Array(100).fill(0).map(() => counter.increment())
)
```

**Python**

```python
class SafeCounter:
  def __init__(self) -> None: ...
  def increment(self) -> None: ...
  def get(self) -> int: ...

counter = SafeCounter()
threads = [
  threading.Thread(target=counter.increment)
  for _ in range(100)
]
for thread in threads:
  thread.start()
for thread in threads:
  thread.join()
```

## Single-threaded mode

By default, BoltFFI enforces two safety rules:

1. Classes must be `Send + Sync`
2. Methods cannot take `&mut self`

Both rules exist because the target language can call your methods from any thread. Without synchronization, concurrent `&mut self` calls cause undefined behavior.

But synchronization has a cost. If you control thread access in the target language - for example, you only use the object from the main thread, or you wrap it in your own synchronization - you're paying for locks you don't need.

`#[export(single_threaded)]` disables both checks:

```rust
pub struct FastCounter {
    value: i32,
}

#[export(single_threaded)]
impl FastCounter {
    pub fn new() -> Self {
        FastCounter { value: 0 }
    }
    
    pub fn increment(&mut self) {
        self.value += 1;
    }
    
    pub fn get(&self) -> i32 {
        self.value
    }
}
```

No synchronization overhead. The tradeoff is that thread safety is now your responsibility. If two threads call methods on this object at the same time, that's undefined behavior.

### When to use single\_threaded

Use `single_threaded` when:

- The object is only accessed from the main thread (UI components, view models)
- You wrap the object in your own synchronization in the target language
- You're building a single-threaded application (WASM, embedded)
- Profiling shows synchronization is a bottleneck and you can guarantee single-threaded access

Don't use it just to avoid writing thread-safe code. The default (`&self` + `Mutex`) is safer and the overhead is often negligible.

### Performance comparison

In benchmarks, `single_threaded` mode is roughly 4x faster for method calls that would otherwise need mutex locks:

| Mode                            | Time per 1000 increments |
| ------------------------------- | ------------------------ |
| `&self` + `Mutex`               | \~5 μs                   |
| `&mut self` + `single_threaded` | \~1 μs                   |

The difference matters in tight loops. For most applications, 5 microseconds per thousand calls is not a bottleneck.

## Memory management

The Rust struct lives in Rust's heap. The target language holds a reference to it. When the target language's object is deallocated (garbage collected, reference count hits zero, etc.), BoltFFI drops the Rust struct.

You don't need to manually free anything. But be aware: the Rust object stays alive as long as the target language holds a reference. If you store a class instance in a long-lived collection, the Rust memory stays allocated.

---

Source: https://boltffi.dev/docs/functions

# Functions

This page covers free functions - standalone functions that aren't attached to a struct or class. For methods on objects, see [Classes](/docs/classes.md). For standalone values, see [Constants](/docs/constants.md).

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](/docs/types.md).

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.

**Rust**

```rust
#[export]
pub fn add(a: i32, b: i32) -> i32 {
  a + b
}

#[export]
pub fn multiply(x: f64, y: f64) -> f64 {
  x * y
}
```

**Swift**

```swift
func add(a: Int32, b: Int32) -> Int32
func multiply(x: Double, y: Double) -> Double

let sum = add(a: 5, b: 3)
let product = multiply(x: 2.5, y: 4.0)
```

**Kotlin**

```kotlin
fun add(a: Int, b: Int): Int
fun multiply(x: Double, y: Double): Double

val sum = add(5, 3)
val product = multiply(2.5, 4.0)
```

**Java**

```java
static int add(int a, int b)
static double multiply(double x, double y)

int sum = add(5, 3);
double product = multiply(2.5, 4.0);
```

**C#**

```csharp
static int Add(int a, int b)
static double Multiply(double x, double y)

int sum = MyLib.Add(5, 3);
double product = MyLib.Multiply(2.5, 4.0);
```

**TypeScript**

```typescript
function add(a: number, b: number): number
function multiply(x: number, y: number): number

const sum = add(5, 3)
const product = multiply(2.5, 4.0)
```

**Python**

```python
def add(a: int, b: int) -> int: ...
def multiply(x: float, y: float) -> float: ...

sum_value = add(5, 3)
product = multiply(2.5, 4.0)
```

## Parameters

### Primitives and strings

Primitive parameters pass directly with no overhead. Strings require copying since each language manages its own memory.

**Rust**

```rust
#[export]
pub fn greet(name: &str) -> String {
  format!("Hello, {}!", name)
}

#[export]
pub fn char_count(s: &str) -> usize {
  s.chars().count()
}
```

**Swift**

```swift
func greet(name: String) -> String
func charCount(s: String) -> UInt

let msg = greet(name: "World")
let len = charCount(s: "hello")
```

**Kotlin**

```kotlin
fun greet(name: String): String
fun charCount(s: String): ULong

val msg = greet("World")
val len = charCount("hello")
```

**Java**

```java
static String greet(String name)
static long charCount(String s)

String msg = greet("World");
long len = charCount("hello");
```

**C#**

```csharp
static string Greet(string name)
static nuint CharCount(string s)

string msg = MyLib.Greet("World");
nuint len = MyLib.CharCount("hello");
```

**TypeScript**

```typescript
function greet(name: string): string
function charCount(s: string): number

const msg = greet("World")
const len = charCount("hello")
```

**Python**

```python
def greet(name: str) -> str: ...
def char_count(s: str) -> int: ...

msg = greet("World")
length = char_count("hello")
```

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.

**Rust**

```rust
#[data]
pub struct Point {
  pub x: f64,
  pub y: f64,
}

#[data]
pub enum Unit {
  Meters,
  Feet,
}

#[export]
pub fn distance(a: Point, b: Point, unit: Unit) -> f64 {
  let dx = b.x - a.x;
  let dy = b.y - a.y;
  let d = (dx*dx + dy*dy).sqrt();
  match unit {
      Unit::Meters => d,
      Unit::Feet => d * 3.28084,
  }
}
```

**Swift**

```swift
public struct Point {
  public var x: Double
  public var y: Double
}

public enum Unit {
  case meters
  case feet
}

func distance(a: Point, b: Point, unit: Unit) -> Double

let d = distance(
  a: Point(x: 0, y: 0),
  b: Point(x: 3, y: 4),
  unit: .meters
)
```

**Kotlin**

```kotlin
data class Point(
  val x: Double,
  val y: Double
)

enum class Unit {
  Meters,
  Feet
}

fun distance(a: Point, b: Point, unit: Unit): Double

val d = distance(
  Point(0.0, 0.0),
  Point(3.0, 4.0),
  Unit.Meters
)
```

**Java**

```java
// Point, Unit generated as records or
// classes based on Java version target
static double distance(
  Point a, Point b, Unit unit)

double d = distance(
  new Point(0.0, 0.0),
  new Point(3.0, 4.0),
  Unit.METERS);
```

**C#**

```csharp
public readonly record struct Point(
  double X,
  double Y
);

public enum Unit
{
  Meters,
  Feet,
}

static double Distance(
  Point a, Point b, Unit unit)

double d = MyLib.Distance(
  new Point(0.0, 0.0),
  new Point(3.0, 4.0),
  Unit.Meters);
```

**TypeScript**

```typescript
interface Point {
  readonly x: number
  readonly y: number
}

enum Unit {
  Meters = 0,
  Feet = 1
}

function distance(a: Point, b: Point, unit: Unit): number

const d = distance(
  { x: 0, y: 0 },
  { x: 3, y: 4 },
  Unit.Meters
)
```

**Python**

```python
from dataclasses import dataclass
from enum import IntEnum

@dataclass
class Point:
  x: float
  y: float

class Unit(IntEnum):
  METERS = 0
  FEET = 1

def distance(a: Point, b: Point, unit: Unit) -> float: ...

d = distance(
  Point(0, 0),
  Point(3, 4),
  Unit.METERS
)
```

### Slices

Use `&[T]` to accept a collection without taking ownership. The caller's array or list is accessible as a slice inside Rust.

**Rust**

```rust
#[export]
pub fn sum(values: &[i32]) -> i32 {
  values.iter().sum()
}

#[export]
pub fn average(values: &[f64]) -> f64 {
  if values.is_empty() {
      return 0.0;
  }
  values.iter().sum::<f64>() / values.len() as f64
}
```

**Swift**

```swift
func sum(values: [Int32]) -> Int32
func average(values: [Double]) -> Double

let total = sum(values: [1, 2, 3, 4, 5])
let avg = average(values: [1.0, 2.0, 3.0])
```

**Kotlin**

```kotlin
fun sum(values: IntArray): Int
fun average(values: DoubleArray): Double

val total = sum(intArrayOf(1, 2, 3, 4, 5))
val avg = average(doubleArrayOf(1.0, 2.0, 3.0))
```

**Java**

```java
static int sum(int[] values)
static double average(double[] values)

int total = sum(new int[]{1, 2, 3, 4, 5});
double avg = average(
  new double[]{1.0, 2.0, 3.0});
```

**C#**

```csharp
static int Sum(int[] values)
static double Average(double[] values)

int total = MyLib.Sum(new[] {1, 2, 3, 4, 5});
double avg = MyLib.Average(
  new[] {1.0, 2.0, 3.0});
```

**TypeScript**

```typescript
function sum(values: number[]): number
function average(values: number[]): number

const total = sum([1, 2, 3, 4, 5])
const avg = average([1.0, 2.0, 3.0])
```

**Python**

```python
def sum(values: list[int]) -> int: ...
def average(values: list[float]) -> float: ...

total = sum([1, 2, 3, 4, 5])
avg = average([1.0, 2.0, 3.0])
```

### Optional

Use `Option<T>` when a parameter might not be provided. The caller passes `nil`/`null` or a value.

**Rust**

```rust
#[export]
pub fn greet_user(
  name: &str,
  title: Option<String>
) -> String {
  match title {
      Some(t) => format!("Hello, {} {}!", t, name),
      None => format!("Hello, {}!", name),
  }
}

#[export]
pub fn set_timeout(
  ms: u64,
  callback_id: Option<u64>
) {
  // ...
}
```

**Swift**

```swift
func greetUser(
  name: String,
  title: String?
) -> String

func setTimeout(
  ms: UInt64,
  callbackId: UInt64?
)

let formal = greetUser(name: "Smith", title: "Dr.")
let casual = greetUser(name: "John", title: nil)
```

**Kotlin**

```kotlin
fun greetUser(
  name: String,
  title: String?
): String

fun setTimeout(
  ms: ULong,
  callbackId: ULong?
)

val formal = greetUser("Smith", "Dr.")
val casual = greetUser("John", null)
```

**Java**

```java
static String greetUser(
  String name,
  java.util.Optional<String> title)

static void setTimeout(
  long ms,
  java.util.Optional<Long> callbackId)

String formal = greetUser(
  "Smith", java.util.Optional.of("Dr."));
String casual = greetUser(
  "John", java.util.Optional.empty());
```

**C#**

```csharp
static string GreetUser(
  string name,
  string? title)

static void SetTimeout(
  ulong ms,
  ulong? callbackId)

string formal = MyLib.GreetUser("Smith", "Dr.");
string casual = MyLib.GreetUser("John", null);
```

**TypeScript**

```typescript
function greetUser(
  name: string,
  title: string | null
): string

function setTimeout(
  ms: bigint,
  callbackId: bigint | null
): void

const formal = greetUser("Smith", "Dr.")
const casual = greetUser("John", null)
```

**Python**

```python
def greet_user(
  name: str,
  title: str | None
) -> str: ...

def set_timeout(
  ms: int,
  callback_id: int | None
) -> None: ...

formal = greet_user("Smith", "Dr.")
casual = greet_user("John", None)
```

### Classes

Functions can accept class instances as parameters. The class reference passes across the boundary without copying the object.

**Rust**

```rust
#[export]
impl Logger {
  pub fn new(prefix: &str) -> Self {
      Logger { prefix: prefix.into() }
  }
}

#[export]
pub fn log_message(
  logger: &Logger,
  message: &str
) {
  println!("[{}] {}", logger.prefix, message);
}
```

**Swift**

```swift
func logMessage(logger: Logger, message: String)

let logger = Logger(prefix: "app")
logMessage(logger: logger, message: "started")
```

**Kotlin**

```kotlin
fun logMessage(logger: Logger, message: String)

val logger = Logger("app")
logMessage(logger, "started")
```

**Java**

```java
static void logMessage(
  Logger logger, String message)

Logger logger = new Logger("app");
logMessage(logger, "started");
```

**C#**

```csharp
static void LogMessage(
  Logger logger, string message)

using Logger logger = new Logger("app");
MyLib.LogMessage(logger, "started");
```

**TypeScript**

```typescript
function logMessage(
  logger: Logger, message: string): void

const logger = Logger.new("app")
logMessage(logger, "started")
```

**Python**

```python
def log_message(
  logger: Logger,
  message: str
) -> None: ...

logger = Logger("app")
log_message(logger, "started")
```

### 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](/docs/callbacks.md) for more.

**Rust**

```rust
#[export]
pub trait EventListener {
  fn on_start(&self);
  fn on_progress(&self, percent: f64);
  fn on_complete(&self, result: String);
}

#[export]
pub fn run_task(
  listener: impl EventListener
) {
  listener.on_start();
  listener.on_progress(0.5);
  listener.on_complete("done".into());
}
```

**Swift**

```swift
protocol EventListener {
  func onStart()
  func onProgress(percent: Double)
  func onComplete(result: String)
}

func runTask(listener: EventListener)

class MyListener: EventListener {
  func onStart() { print("started") }
  func onProgress(percent: Double) {
      print("\(percent * 100)%")
  }
  func onComplete(result: String) {
      print(result)
  }
}

runTask(listener: MyListener())
```

**Kotlin**

```kotlin
interface EventListener {
  fun onStart()
  fun onProgress(percent: Double)
  fun onComplete(result: String)
}

fun runTask(listener: EventListener)

val listener = object : EventListener {
  override fun onStart() =
      println("started")
  override fun onProgress(percent: Double) =
      println("${percent * 100}%")
  override fun onComplete(result: String) =
      println(result)
}

runTask(listener)
```

**Java**

```java
public interface EventListener {
  void onStart();
  void onProgress(double percent);
  void onComplete(String result);
}

static void runTask(EventListener listener)

runTask(new EventListener() {
  public void onStart() {
      System.out.println("started");
  }
  public void onProgress(double percent) {
      System.out.println(percent * 100 + "%");
  }
  public void onComplete(String result) {
      System.out.println(result);
  }
});
```

**C#**

```csharp
public interface EventListener
{
  void OnStart();
  void OnProgress(double percent);
  void OnComplete(string result);
}

static void RunTask(EventListener listener)

public sealed class MyListener : EventListener
{
  public void OnStart() =>
      Console.WriteLine("started");
  public void OnProgress(double percent) =>
      Console.WriteLine(percent * 100 + "%");
  public void OnComplete(string result) =>
      Console.WriteLine(result);
}

MyLib.RunTask(new MyListener());
```

**TypeScript**

```typescript
interface EventListener {
  onStart(): void
  onProgress(percent: number): void
  onComplete(result: string): void
}

function runTask(listener: EventListener): void

runTask({
  onStart() { console.log("started") },
  onProgress(percent) {
      console.log(percent * 100 + "%")
  },
  onComplete(result) {
      console.log(result)
  }
})
```

**Python**

```python
class EventListener:
  def on_start(self) -> None: ...
  def on_progress(self, percent: float) -> None: ...
  def on_complete(self, result: str) -> None: ...

def run_task(listener: EventListener) -> None: ...

class MyListener(EventListener):
  def on_start(self) -> None:
      print("started")
  def on_progress(self, percent: float) -> None:
      print(f"{percent * 100}%")
  def on_complete(self, result: str) -> None:
      print(result)

run_task(MyListener())
```

## Return types

### Option

Return `Option<T>` when a value might not exist. The caller gets a nullable type they can check before using.

**Rust**

```rust
#[export]
pub fn find_user(id: u64) -> Option<User> {
  database.get(&id).cloned()
}

#[export]
pub fn first_positive(values: &[i32]) -> Option<i32> {
  values.iter().copied().find(|&x| x > 0)
}
```

**Swift**

```swift
func findUser(id: UInt64) -> User?
func firstPositive(values: [Int32]) -> Int32?

if let user = findUser(id: 42) {
  print(user.name)
}

let pos = firstPositive(values: [-1, -2, 3])
```

**Kotlin**

```kotlin
fun findUser(id: ULong): User?
fun firstPositive(values: IntArray): Int?

findUser(42uL)?.let { user ->
  println(user.name)
}

val pos = firstPositive(intArrayOf(-1, -2, 3))
```

**Java**

```java
static java.util.Optional<User> findUser(
  long id)
static java.util.Optional<Integer> firstPositive(
  int[] values)

findUser(42L).ifPresent(user ->
  System.out.println(user.name()));

java.util.Optional<Integer> pos =
  firstPositive(new int[]{-1, -2, 3});
```

**C#**

```csharp
static User? FindUser(ulong id)
static int? FirstPositive(int[] values)

User? user = MyLib.FindUser(42);
if (user is not null) {
  Console.WriteLine(user.Name);
}

int? pos = MyLib.FirstPositive(
  new[] {-1, -2, 3});
```

**TypeScript**

```typescript
function findUser(id: bigint): User | null
function firstPositive(values: number[]): number | null

const user = findUser(42n)
if (user !== null) {
  console.log(user.name)
}

const pos = firstPositive([-1, -2, 3])
```

**Python**

```python
def find_user(id: int) -> User | None: ...
def first_positive(values: list[int]) -> int | None: ...

user = find_user(42)
if user is not None:
  print(user.name)

pos = first_positive([-1, -2, 3])
```

### 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.

**Rust**

```rust
#[error]
pub enum ParseError {
  InvalidFormat,
  OutOfRange,
}

#[export]
pub fn parse_port(s: &str) -> Result<u16, ParseError> {
  let n: u32 = s.parse()
      .map_err(|_| ParseError::InvalidFormat)?;
  if n > 65535 {
      return Err(ParseError::OutOfRange);
  }
  Ok(n as u16)
}
```

**Swift**

```swift
public enum ParseError: Error {
  case invalidFormat
  case outOfRange
}

func parsePort(s: String) throws -> UInt16

do {
  let port = try parsePort(s: "8080")
} catch ParseError.invalidFormat {
  print("bad format")
} catch ParseError.outOfRange {
  print("too large")
}
```

**Kotlin**

```kotlin
sealed class ParseError : Exception() {
  object InvalidFormat : ParseError()
  object OutOfRange : ParseError()
}

@Throws(ParseError::class)
fun parsePort(s: String): UShort

try {
  val port = parsePort("8080")
} catch (e: ParseError.InvalidFormat) {
  println("bad format")
} catch (e: ParseError.OutOfRange) {
  println("too large")
}
```

**Java**

```java
public enum ParseError {
  INVALID_FORMAT,
  OUT_OF_RANGE;

  public static final class Exception
      extends RuntimeException {
      public ParseError getError()
  }
}

static short parsePort(String s)
  // throws ParseError.Exception

try {
  short port = parsePort("8080");
} catch (ParseError.Exception e) {
  if (e.getError() == ParseError.INVALID_FORMAT)
      System.out.println("bad format");
  else if (e.getError() == ParseError.OUT_OF_RANGE)
      System.out.println("too large");
}
```

**C#**

```csharp
public enum ParseError
{
  InvalidFormat,
  OutOfRange,
}

public sealed class ParseErrorException : Exception
{
  public ParseError Error { get; }
}

static ushort ParsePort(string s)

try {
  ushort port = MyLib.ParsePort("8080");
} catch (ParseErrorException e) {
  if (e.Error == ParseError.InvalidFormat)
      Console.WriteLine("bad format");
  else if (e.Error == ParseError.OutOfRange)
      Console.WriteLine("too large");
}
```

**TypeScript**

```typescript
enum ParseError {
  InvalidFormat = 0,
  OutOfRange = 1
}

class ParseErrorException extends Error {
  readonly code: ParseError
}

function parsePort(s: string): number  // throws

try {
  const port = parsePort("8080")
} catch (e) {
  if (e instanceof ParseErrorException) {
      if (e.code === ParseError.InvalidFormat) {
          console.log("bad format")
      } else if (e.code === ParseError.OutOfRange) {
          console.log("too large")
      }
  }
}
```

**Python**

```python
from enum import IntEnum

class ParseError(IntEnum):
  INVALID_FORMAT = 0
  OUT_OF_RANGE = 1

class ParseErrorException(RuntimeError):
  error: ParseError

def parse_port(s: str) -> int: ...

try:
  port = parse_port("8080")
except ParseErrorException as error:
  if error.error == ParseError.INVALID_FORMAT:
      print("bad format")
  elif error.error == ParseError.OUT_OF_RANGE:
      print("too large")
```

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.

**Rust**

```rust
#[export]
pub fn range(start: i32, end: i32) -> Vec<i32> {
  (start..end).collect()
}

#[export]
pub fn filter_positive(values: &[i32]) -> Vec<i32> {
  values.iter().copied().filter(|&x| x > 0).collect()
}
```

**Swift**

```swift
func range(start: Int32, end: Int32) -> [Int32]
func filterPositive(values: [Int32]) -> [Int32]

let nums = range(start: 0, end: 10)
let pos = filterPositive(values: [-1, 2, -3, 4])
```

**Kotlin**

```kotlin
fun range(start: Int, end: Int): IntArray
fun filterPositive(values: IntArray): IntArray

val nums = range(0, 10)
val pos = filterPositive(intArrayOf(-1, 2, -3, 4))
```

**Java**

```java
static int[] range(int start, int end)
static int[] filterPositive(int[] values)

int[] nums = range(0, 10);
int[] pos = filterPositive(
  new int[]{-1, 2, -3, 4});
```

**C#**

```csharp
static int[] Range(int start, int end)
static int[] FilterPositive(int[] values)

int[] nums = MyLib.Range(0, 10);
int[] pos = MyLib.FilterPositive(
  new[] {-1, 2, -3, 4});
```

**TypeScript**

```typescript
function range(start: number, end: number): number[]
function filterPositive(values: number[]): number[]

const nums = range(0, 10)
const pos = filterPositive([-1, 2, -3, 4])
```

**Python**

```python
def range(start: int, end: int) -> list[int]: ...
def filter_positive(values: list[int]) -> list[int]: ...

nums = range(0, 10)
pos = filter_positive([-1, 2, -3, 4])
```

## Async functions

Mark a function `async` and BoltFFI generates an async function in the target language:

- Swift: `async`
- Kotlin: `suspend`
- Java: `CompletableFuture` on Java 8+, with virtual-thread blocking calls on Java 21+
- C#: `Task`
- TypeScript: `Promise`

**Rust**

```rust
#[export]
pub async fn fetch_data(url: &str) -> Result<String, FetchError> {
  let response = client.get(url).await?;
  let body = response.text().await?;
  Ok(body)
}

#[export]
pub async fn load_config() -> Config {
  let bytes = read_file("config.json").await;
  parse_config(&bytes)
}
```

**Swift**

```swift
func fetchData(url: String) async throws -> String
func loadConfig() async -> Config

Task {
  let data = try await fetchData(url: "https://api.example.com")
  let config = await loadConfig()
}
```

**Kotlin**

```kotlin
suspend fun fetchData(url: String): String
suspend fun loadConfig(): Config

coroutineScope {
  val data = fetchData("https://api.example.com")
  val config = loadConfig()
}
```

**Java**

```java
// Java 21+ (virtual threads)
static String fetchData(String url)
  // throws FetchErrorException
static Config loadConfig()

// Java 8+ (CompletableFuture)
static CompletableFuture<String> fetchData(
  String url)
static CompletableFuture<Config> loadConfig()

String data = fetchData(
  "https://api.example.com");
```

**C#**

```csharp
static Task<string> FetchData(string url)
static Task<Config> LoadConfig()

string data = await MyLib.FetchData(
  "https://api.example.com");
Config config = await MyLib.LoadConfig();
```

**TypeScript**

```typescript
async function fetchData(url: string): Promise<string>
async function loadConfig(): Promise<Config>

const data = await fetchData("https://api.example.com")
const config = await loadConfig()
```

**Python**

```python
async def fetch_data(url: str) -> str: ...
async def load_config() -> Config: ...

data = await fetch_data("https://api.example.com")
config = await load_config()
```

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](/docs/async.md) for more.

## Closures

Functions can accept closures as parameters. The closure is called synchronously within the Rust function.

**Rust**

```rust
#[export]
pub fn foreach_range(
  start: i32,
  end: i32,
  mut callback: impl FnMut(i32)
) {
  (start..end).for_each(|i| callback(i));
}

#[export]
pub fn map_values(
  values: &[i32],
  transform: impl Fn(i32) -> i32
) -> Vec<i32> {
  values.iter().map(|&x| transform(x)).collect()
}
```

**Swift**

```swift
func foreachRange(
  start: Int32,
  end: Int32,
  callback: (Int32) -> Void
)

func mapValues(
  values: [Int32],
  transform: (Int32) -> Int32
) -> [Int32]

var sum: Int32 = 0
foreachRange(start: 1, end: 5) { sum += $0 }

let doubled = mapValues(values: [1, 2, 3]) { $0 * 2 }
```

**Kotlin**

```kotlin
fun foreachRange(
  start: Int,
  end: Int,
  callback: (Int) -> Unit
)

fun mapValues(
  values: IntArray,
  transform: (Int) -> Int
): IntArray

var sum = 0
foreachRange(1, 5) { sum += it }

val doubled = mapValues(intArrayOf(1, 2, 3)) { it * 2 }
```

**Java**

```java
// @FunctionalInterface generated for each
// closure signature
static void foreachRange(
  int start, int end,
  ForeachRangeCallback callback)
static int[] mapValues(
  int[] values,
  MapValuesTransform transform)

int[] sum = {0};
foreachRange(1, 5, v -> sum[0] += v);

int[] doubled = mapValues(
  new int[]{1, 2, 3}, v -> v * 2);
```

**C#**

```csharp
public delegate void ForeachRangeCallback(int value);
public delegate int MapValuesTransform(int value);

static void ForeachRange(
  int start, int end,
  ForeachRangeCallback callback)
static int[] MapValues(
  int[] values,
  MapValuesTransform transform)

int sum = 0;
MyLib.ForeachRange(1, 5, v => sum += v);

int[] doubled = MyLib.MapValues(
  new[] {1, 2, 3}, v => v * 2);
```

**TypeScript**

```typescript
function foreachRange(
  start: number,
  end: number,
  callback: (value: number) => void
): void

function mapValues(
  values: number[],
  transform: (value: number) => number
): number[]

let sum = 0
foreachRange(1, 5, (v) => { sum += v })

const doubled = mapValues([1, 2, 3], (v) => v * 2)
```

**Python**

```python
from collections.abc import Callable

def foreach_range(
  start: int,
  end: int,
  callback: Callable[[int], None]
) -> None: ...

def map_values(
  values: list[int],
  transform: Callable[[int], int]
) -> list[int]: ...

total = 0
def add_value(value: int) -> None:
  global total
  total += value

foreach_range(1, 5, add_value)
doubled = map_values([1, 2, 3], lambda value: value * 2)
```

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) -> T` are 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.

```rust
// 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 }
}
```

---

Source: https://boltffi.dev/docs/constants

# Constants

BoltFFI exports two kinds of constants:

- A global constant is a standalone Rust `const` marked with `#[export]`.
- An associated constant belongs to an exported record, enum, or class.

Both forms produce immutable values in the generated API. BoltFFI emits simple literals directly in generated source and reads values that require Rust evaluation through a generated native accessor.

Use a global constant for a value that belongs to the package as a whole. Use an associated constant when the value describes a particular record, enum, or class.

## Global constants

Mark a standalone public constant with `#[export]`. Swift, Kotlin, TypeScript, and Python expose it at module scope. Java and C# place it on the generated module class because those languages do not have module-level values.

**Rust**

```rust
#[export]
pub const API_VERSION: u32 = 3;

#[export]
pub const SERVICE_NAME: &'static str = "BoltFFI";
```

**Swift**

```swift
public let apiVersion: UInt32 = 3
public let serviceName: String = "BoltFFI"

let version = apiVersion
```

**Kotlin**

```kotlin
val apiVersion: UInt = 3.toUInt()
val serviceName: String = "BoltFFI"

val version = apiVersion
```

**Java**

```java
public final class Demo {
  public static final int API_VERSION = 3;
  public static final String SERVICE_NAME = "BoltFFI";
}

int version = Demo.API_VERSION;
```

**C#**

```csharp
public static class Demo
{
  public const uint ApiVersion = 3U;
  public const string ServiceName = "BoltFFI";
}

uint version = Demo.ApiVersion;
```

**TypeScript**

```typescript
export const apiVersion: number = 3
export const serviceName: string = "BoltFFI"

const version = apiVersion
```

**Python**

```python
api_version: int = 3
service_name: str = "BoltFFI"

version = api_version
```

The Rust name is converted to the naming convention of each target. `API_VERSION` becomes `apiVersion` in Swift, Kotlin, and TypeScript, `API_VERSION` in Java, `ApiVersion` in C#, and `api_version` in Python.

## Associated constants

Associated constants are declared inside the same exported impl blocks used for methods and constructors:

- Use `#[data(impl)]` for a struct or enum marked with `#[data]`.
- Use `#[export]` for a class impl.

Every public constant in the marked impl is generated on its owner type. The constant may use `Self` or any other supported constant type. The impl marker exports the member, so the individual constant does not need another `#[export]` attribute.

**Rust**

```rust
#[data]
pub struct Color {
  pub r: u8,
  pub g: u8,
  pub b: u8,
  pub a: u8,
}

#[data(impl)]
impl Color {
  pub const BLACK: Self = Self {
      r: 0,
      g: 0,
      b: 0,
      a: 255,
  };

  pub const CHANNEL_COUNT: u8 = 4;
}

#[repr(u8)]
#[data]
pub enum Mode {
  Fast = 1,
  Slow = 2,
}

#[data(impl)]
impl Mode {
  pub const DEFAULT: Self = Self::Fast;
}

pub struct Palette;

#[export]
impl Palette {
  pub const MAX_COLORS: u8 = 16;

  pub fn new() -> Self {
      Self
  }
}
```

**Swift**

```swift
public struct Color {
  public var r: UInt8
  public var g: UInt8
  public var b: UInt8
  public var a: UInt8

  public static var black: Color { get }
  public static let channelCount: UInt8
}

public enum Mode: UInt8 {
  case fast = 1
  case slow = 2

  public static let `default`: Mode
}

public final class Palette {
  public static let maxColors: UInt8
}

let color = Color.black
let mode = Mode.default
let limit = Palette.maxColors
```

**Kotlin**

```kotlin
data class Color(
  val r: UByte,
  val g: UByte,
  val b: UByte,
  val a: UByte,
) {
  companion object {
      val BLACK: Color
      val CHANNEL_COUNT: UByte
  }
}

enum class Mode(val value: Byte) {
  FAST(1.toByte()),
  SLOW(2.toByte());

  companion object {
      val DEFAULT: Mode
  }
}

class Palette {
  companion object {
      val MAX_COLORS: UByte
  }
}

val color = Color.BLACK
val mode = Mode.DEFAULT
val limit = Palette.MAX_COLORS
```

**Java**

```java
public final class Color {
  public final byte r;
  public final byte g;
  public final byte b;
  public final byte a;

  public static final Color BLACK;
  public static final byte CHANNEL_COUNT = (byte) 4;
}

public enum Mode {
  FAST((byte) 1),
  SLOW((byte) 2);

  public static final Mode DEFAULT = Mode.FAST;
}

public final class Palette implements AutoCloseable {
  public static final byte MAX_COLORS = (byte) 16;
}

Color color = Color.BLACK;
Mode mode = Mode.DEFAULT;
byte limit = Palette.MAX_COLORS;
```

**C#**

```csharp
public readonly record struct Color(
  byte R,
  byte G,
  byte B,
  byte A)
{
  public static Color Black { get; }
  public const byte ChannelCount = 4;
}

public enum Mode : byte
{
  Fast = 1,
  Slow = 2,
  Default = Fast,
}

public sealed class Palette : IDisposable
{
  public const byte MaxColors = 16;
}

Color color = Color.Black;
Mode mode = Mode.Default;
byte limit = Palette.MaxColors;
```

**TypeScript**

```typescript
export interface Color {
readonly r: number
readonly g: number
readonly b: number
readonly a: number
}

export const Color: {
readonly BLACK: Color
readonly CHANNEL_COUNT: number
}

export type Mode = 1 | 2

export const Mode: {
readonly Fast: 1
readonly Slow: 2
readonly DEFAULT: 1
}

export class Palette {
static readonly MAX_COLORS: number
}

const color = Color.BLACK
const mode: Mode = Mode.DEFAULT
const limit = Palette.MAX_COLORS
```

**Python**

```python
from dataclasses import dataclass
from enum import IntEnum
from typing import ClassVar

@dataclass(frozen=True)
class Color:
  BLACK: ClassVar["Color"]
  CHANNEL_COUNT: ClassVar[int]

  r: int
  g: int
  b: int
  a: int

class Mode(IntEnum):
  FAST = 1
  SLOW = 2

  DEFAULT: ClassVar["Mode"]

class Palette:
  MAX_COLORS: ClassVar[int]

color = Color.BLACK
mode = Mode.DEFAULT
limit = Palette.MAX_COLORS
```

`Color.BLACK` uses `Self`, `Color.CHANNEL_COUNT` uses a primitive, `Mode.DEFAULT` aliases an enum variant, and `Palette.MAX_COLORS` belongs to a Rust-backed class. In every target, the value remains attached to the type that owns it.

## Data constants

Every record or enum marked with `#[data]` can be the value type of a global or associated constant. This includes primitive-only records, encoded records, C-style enums, and data enums. The selected target must already support that data type as a normal return value.

The `Color` and `Mode` declarations above are examples of associated data constants. The same data types also work as global constants:

**Rust**

```rust
#[export]
pub const DEFAULT_COLOR: Color = Color {
  r: 30,
  g: 30,
  b: 30,
  a: 255,
};

#[export]
pub const DEFAULT_MODE: Mode = Mode::Fast;
```

**Swift**

```swift
public var defaultColor: Color { get }
public let defaultMode: Mode = Mode.fast

let color = defaultColor
let mode = defaultMode
```

**Kotlin**

```kotlin
val defaultColor: Color
  get()

val defaultMode: Mode = Mode.FAST

val color = defaultColor
val mode = defaultMode
```

**Java**

```java
public final class Demo {
  public static final Color DEFAULT_COLOR;
  public static final Mode DEFAULT_MODE = Mode.FAST;
}

Color color = Demo.DEFAULT_COLOR;
Mode mode = Demo.DEFAULT_MODE;
```

**C#**

```csharp
public static class Demo
{
  public static Color DefaultColor { get; }
  public const Mode DefaultMode = Mode.Fast;
}

Color color = Demo.DefaultColor;
Mode mode = Demo.DefaultMode;
```

**TypeScript**

```typescript
export let defaultColor: Color
export const defaultMode: Mode = Mode.Fast

const color = defaultColor
const mode = defaultMode
```

**Python**

```python
default_color: Color
default_mode: Mode = Mode.FAST

color = default_color
mode = default_mode
```

Values such as `Color { ... }` are constructed in Rust and delivered through a generated accessor. The generated API still exposes an ordinary immutable value.

## Supported values

Constants can use the same types as exported function results, including primitives, strings, byte slices, tuples, and types marked with `#[data]`. Associated constants can use `Self` for values of their owner type.

---

Source: https://boltffi.dev/docs/async

# Async

Rust async functions can be exported just like regular functions. Add `async` to the function signature and BoltFFI handles bridging the future to the target language's async model. The generated bindings let callers await the result using native syntax, with no manual callback wiring or polling logic on either side. Cancellation propagates from the caller back to Rust, and errors work the same as synchronous functions.

**Rust**

```rust
#[export]
pub async fn fetch_user(id: i32) -> User {
  db.query_user(id).await
}
```

**Swift**

```swift
// Generated
func fetchUser(id: Int32) async -> User

// Usage
let user = await fetchUser(id: 42)
```

**Kotlin**

```kotlin
// Generated
suspend fun fetchUser(id: Int): User

// Usage
val user = fetchUser(42)
```

**Java**

```java
// Java 21+ (virtual threads)
static User fetchUser(int id)

User user = fetchUser(42);

// Java 8+ (CompletableFuture)
static CompletableFuture<User> fetchUser(
  int id)

User user = fetchUser(42).join();
```

**C#**

```csharp
// Generated
static Task<User> FetchUser(int id)
static Task<User> FetchUser(
  int id,
  CancellationToken cancellationToken)

// Usage
User user = await MyLib.FetchUser(42);
```

**TypeScript**

```typescript
// Generated
async function fetchUser(id: number): Promise<User>

// Usage
const user = await fetchUser(42)
```

**Python**

```python
# Generated
async def fetch_user(id: int) -> User: ...

# Usage
user = await fetch_user(42)
```

## How It Works

BoltFFI uses continuation-based polling to bridge Rust futures to target languages. When you call an async function, BoltFFI creates a handle to the future and returns it immediately. The target language's async runtime then polls this handle, passing a continuation callback. When the Rust future makes progress or completes, the callback fires and the target language resumes execution. This approach is lock-free with no busy-waiting, and the target language drives the entire polling lifecycle.

See [Async Internals](/docs/async-internals.md) for the full implementation details including the FFI protocol and sequence diagram.

## Standalone Functions

Standalone async functions work like regular exported functions with the `async` keyword added. The function runs on the target language's async context and returns when the Rust future completes. Parameters are captured when the function is called, and the result is delivered through the native async mechanism.

**Rust**

```rust
#[export]
pub async fn load_config(path: &str) -> Config {
  read_file(path).await
}
```

**Swift**

```swift
// Generated
func loadConfig(path: String) async -> Config

// Usage
let config = await loadConfig(path: "settings.json")
```

**Kotlin**

```kotlin
// Generated
suspend fun loadConfig(path: String): Config

// Usage
val config = loadConfig("settings.json")
```

**Java**

```java
// Java 21+ (virtual threads)
static Config loadConfig(String path)

Config config = loadConfig("settings.json");

// Java 8+ (CompletableFuture)
static CompletableFuture<Config> loadConfig(
  String path)

Config config =
  loadConfig("settings.json").join();
```

**C#**

```csharp
// Generated
static Task<Config> LoadConfig(string path)
static Task<Config> LoadConfig(
  string path,
  CancellationToken cancellationToken)

// Usage
Config config =
  await MyLib.LoadConfig("settings.json");
```

**TypeScript**

```typescript
// Generated
async function loadConfig(path: string): Promise<Config>

// Usage
const config = await loadConfig("settings.json")
```

**Python**

```python
# Generated
async def load_config(path: str) -> Config: ...

# Usage
config = await load_config("settings.json")
```

## Methods

Methods on classes can be async. The object reference is captured along with any parameters when the method is called. Both `&self` and `&mut self` methods can be async, but the same thread safety rules apply: `&mut self` requires `#[export(single_threaded)]` since the target language can call from any thread. See [Classes - \&self vs \&mut self](/docs/classes.md#self-vs-mut-self) for details.

**Rust**

```rust
pub struct Database {
  connection: Connection,
}

#[export]
impl Database {
  pub fn connect(url: &str) -> Self {
      Database {
          connection: Connection::new(url)
      }
  }
  
  pub async fn query(&self, sql: &str) -> Vec<Row> {
      self.connection.execute(sql).await
  }
}
```

**Swift**

```swift
// Generated
public class Database {
  public static func connect(url: String) -> Database
  public func query(sql: String) async -> [Row]
}

// Usage
let db = Database.connect(url: "postgres://...")
let rows = await db.query(sql: "SELECT * FROM users")
```

**Kotlin**

```kotlin
// Generated
class Database {
  companion object {
      fun connect(url: String): Database
  }
  suspend fun query(sql: String): List<Row>
}

// Usage
val db = Database.connect("postgres://...")
val rows = db.query("SELECT * FROM users")
```

**Java**

```java
// Java 21+ (virtual threads)
public final class Database
  implements AutoCloseable {
  public static Database connect(String url)
  public java.util.List<Row> query(String sql)
}

Database db = Database.connect("postgres://...");
java.util.List<Row> rows =
  db.query("SELECT * FROM users");

// Java 8+ (CompletableFuture)
public CompletableFuture<java.util.List<Row>>
  query(String sql)

db.query("SELECT * FROM users")
  .thenAccept(rows ->
      System.out.println(rows.size()));
```

**C#**

```csharp
// Generated
public sealed class Database : IDisposable
{
  public static Database Connect(string url)
  public Task<Row[]> Query(string sql)
}

// Usage
using Database db =
  Database.Connect("postgres://...");
Row[] rows =
  await db.Query("SELECT * FROM users");
```

**TypeScript**

```typescript
// Generated
declare class Database {
  private constructor()
  static connect(url: string): Database
  async query(sql: string): Promise<Row[]>
  dispose(): void
}

// Usage
const db = Database.connect("postgres://...")
const rows = await db.query("SELECT * FROM users")
```

**Python**

```python
# Generated
class Database:
  @classmethod
  def connect(cls, url: str) -> "Database": ...
  async def query(self, sql: str) -> list[Row]: ...
  def close(self) -> None: ...

# Usage
db = Database.connect("postgres://...")
rows = await db.query("SELECT * FROM users")
```

## Error Handling

Async functions can return `Result` just like synchronous functions. The error is delivered through the target language's native error handling mechanism when the async operation completes. The same error types and conversion rules apply as described in [Errors](/docs/errors.md).

**Rust**

```rust
#[export]
pub async fn fetch_user(id: i32) -> Result<User, DbError> {
  db.query_user(id).await
}
```

**Swift**

```swift
// Generated
func fetchUser(id: Int32) async throws -> User

// Usage
do {
  let user = try await fetchUser(id: 42)
} catch {
  print("Failed: \(error)")
}
```

**Kotlin**

```kotlin
// Generated
@Throws(FfiException::class)
suspend fun fetchUser(id: Int): User

// Usage
try {
  val user = fetchUser(42)
} catch (e: FfiException) {
  println("Failed: ${e.message}")
}
```

**Java**

```java
// Java 21+ (virtual threads)
static User fetchUser(int id)
  // throws DbError.Exception

try {
  User user = fetchUser(42);
} catch (DbError.Exception e) {
  System.out.println("Failed: "
      + e.getError());
}

// Java 8+ (CompletableFuture)
static CompletableFuture<User> fetchUser(
  int id)

fetchUser(42)
  .exceptionally(e -> {
      System.out.println("Failed: " + e);
      return null;
  });
```

**C#**

```csharp
// Generated
static Task<User> FetchUser(int id)

// Usage
try {
  User user = await MyLib.FetchUser(42);
} catch (DbErrorException e) {
  Console.WriteLine("Failed: " + e.Error);
}
```

**TypeScript**

```typescript
// Generated
async function fetchUser(id: number): Promise<User>  // throws

// Usage
try {
  const user = await fetchUser(42)
} catch (e) {
  console.log("Failed:", e)
}
```

**Python**

```python
# Generated
async def fetch_user(id: int) -> User: ...

# Usage
try:
  user = await fetch_user(42)
except DbErrorException as error:
  print("Failed:", error)
```

## Cancellation

Cancellation in the target language propagates back to Rust. When a caller cancels an async operation, the future is marked as cancelled, and the next poll returns immediately without running more of the future's code. This is cooperative cancellation, not preemption. The future is not forcibly aborted mid-execution, so any cleanup code after an await point may not run if cancellation happens before reaching it.

**Rust**

```rust
#[export]
pub async fn sync_all() -> SyncResult {
  // sync work
}
```

**Swift**

```swift
// Generated
func syncAll() async -> SyncResult

// Usage
let task = Task {
  await syncAll()
}
// later
task.cancel()
```

**Kotlin**

```kotlin
// Generated
suspend fun syncAll(): SyncResult

// Usage
val job = scope.launch {
  syncAll()
}
// later
job.cancel()
```

**Java**

```java
// Java 21+ (virtual threads)
static SyncResult syncAll()

Thread thread = Thread.startVirtualThread(
  () -> syncAll());
// later
thread.interrupt();

// Java 8+ (CompletableFuture)
static CompletableFuture<SyncResult> syncAll()

CompletableFuture<SyncResult> future =
  syncAll();
// later
future.cancel(true);
```

**C#**

```csharp
// Generated
static Task<SyncResult> SyncAll()
static Task<SyncResult> SyncAll(
  CancellationToken cancellationToken)

// Usage
using var cts = new CancellationTokenSource();
Task<SyncResult> task = MyLib.SyncAll(cts.Token);
// later
cts.Cancel();
```

**TypeScript**

```typescript
// Generated
async function syncAll(): Promise<SyncResult>

// Usage
const promise = syncAll()
const result = await promise
```

**Python**

```python
# Generated
async def sync_all() -> SyncResult: ...

# Usage
task = asyncio.create_task(sync_all())
# later
task.cancel()
```

## Runtime

BoltFFI wraps your future and lets the target language drive polling. BoltFFI itself does not require a Rust async runtime. The polling mechanism is built into the generated bindings and works without tokio, async-std, or any other executor.

However, if your async code uses libraries that depend on a runtime, that runtime must be available. Pure computation and channel-based async works without any runtime. Libraries like `reqwest` or `tokio::fs` require tokio's reactor to be running because they rely on it for I/O. If you use such libraries, you need to ensure a tokio runtime is active when your async functions execute.

---

Source: https://boltffi.dev/docs/callbacks

# Callbacks & Traits

Target language code can flow into Rust through closures and traits. Pass a closure and the target language provides a lambda. Define a trait and the target language implements it as a protocol or interface. Both enable patterns like event handling, progress reporting, data providers, and transformation functions. BoltFFI generates the appropriate bindings for each form, handling the FFI boundary so implementations on either side can call each other safely.

## Closures

Use closures when you need a single callback function. Accept `impl Fn`, `impl FnMut`, or `impl FnOnce` as a parameter, and BoltFFI generates a function type in the target language. The caller passes a lambda or closure, and Rust invokes it during execution. This works for iteration, mapping, filtering, and simple event handlers. Closures work in both standalone functions and class methods.

**Rust**

```rust
#[export]
pub fn foreach_item(
  items: &[i32],
  mut callback: impl FnMut(i32)
) {
  items.iter().for_each(|&x| callback(x));
}
```

**Swift**

```swift
func foreachItem(
  items: [Int32],
  callback: (Int32) -> Void
)

var sum: Int32 = 0
foreachItem(items: [1, 2, 3]) { value in
  sum += value
}
```

**Kotlin**

```kotlin
fun foreachItem(
  items: IntArray,
  callback: (Int) -> Unit
)

var sum = 0
foreachItem(intArrayOf(1, 2, 3)) { value ->
  sum += value
}
```

**Java**

```java
@FunctionalInterface
public interface ForeachItemCallback {
  void invoke(int value);
}

static void foreachItem(
  int[] items,
  ForeachItemCallback callback)

int[] sum = {0};
foreachItem(
  new int[]{1, 2, 3},
  v -> sum[0] += v);
```

**C#**

```csharp
public delegate void ForeachItemCallback(int value);

static void ForeachItem(
  int[] items,
  ForeachItemCallback callback)

int sum = 0;
MyLib.ForeachItem(
  new[] {1, 2, 3},
  value => sum += value);
```

**TypeScript**

```typescript
type ForeachCallback = (value: number) => void

function foreachItem(
  items: number[],
  callback: ForeachCallback
): void

let sum = 0
foreachItem([1, 2, 3], (value) => {
  sum += value
})
```

**Python**

```python
from collections.abc import Callable

ForeachCallback = Callable[[int], None]

def foreach_item(
  items: list[int],
  callback: ForeachCallback
) -> None: ...

total = 0
def add_value(value: int) -> None:
  global total
  total += value

foreach_item([1, 2, 3], add_value)
```

### Return Values

Closures can return values. The return type becomes part of the generated function signature.

**Rust**

```rust
#[export]
pub fn transform(
  items: &[i32],
  f: impl Fn(i32) -> i32
) -> Vec<i32> {
  items.iter().map(|&x| f(x)).collect()
}
```

**Swift**

```swift
func transform(
  items: [Int32],
  f: (Int32) -> Int32
) -> [Int32]

let doubled = transform(items: [1, 2, 3]) { $0 * 2 }
```

**Kotlin**

```kotlin
fun transform(
  items: IntArray,
  f: (Int) -> Int
): IntArray

val doubled = transform(intArrayOf(1, 2, 3)) { it * 2 }
```

**Java**

```java
@FunctionalInterface
public interface TransformF {
  int invoke(int value);
}

static int[] transform(
  int[] items, TransformF f)

int[] doubled = transform(
  new int[]{1, 2, 3}, v -> v * 2);
```

**C#**

```csharp
public delegate int TransformF(int value);

static int[] Transform(
  int[] items, TransformF f)

int[] doubled = MyLib.Transform(
  new[] {1, 2, 3}, v => v * 2);
```

**TypeScript**

```typescript
type TransformFn = (value: number) => number

function transform(
  items: number[],
  f: TransformFn
): number[]

const doubled = transform([1, 2, 3], (v) => v * 2)
```

**Python**

```python
def transform(
  items: Sequence[int],
  f: Callable[..., object]
) -> list[int]: ...

doubled = transform([1, 2, 3], lambda value: value * 2)
```

## Traits

Use traits when you need multiple related callback methods. Define a trait with `#[export]` and BoltFFI generates a protocol or interface in the target language. The target language implements this protocol, and Rust receives an object that can call any of its methods. This pattern works well for progress handlers, event listeners, and delegate protocols. Like closures, trait callbacks work in both standalone functions and class methods.

**Rust**

```rust
#[export]
pub trait ProgressHandler {
  fn on_progress(&self, percent: f64);
  fn on_complete(&self, result: &str);
  fn on_error(&self, error: &str);
}

#[export]
pub fn download(
  url: &str,
  handler: Box<dyn ProgressHandler>
) {
  handler.on_progress(0.0);
  // ... work ...
  handler.on_complete("Done");
}
```

**Swift**

```swift
protocol ProgressHandler {
  func onProgress(percent: Double)
  func onComplete(result: String)
  func onError(error: String)
}

func download(
  url: String,
  handler: ProgressHandler
)

class MyHandler: ProgressHandler {
  func onProgress(percent: Double) {
      print("Progress: \(percent)%")
  }
  func onComplete(result: String) {
      print("Done: \(result)")
  }
  func onError(error: String) {
      print("Error: \(error)")
  }
}

download(url: "https://example.com",
  handler: MyHandler())
```

**Kotlin**

```kotlin
interface ProgressHandler {
  fun onProgress(percent: Double)
  fun onComplete(result: String)
  fun onError(error: String)
}

fun download(
  url: String,
  handler: ProgressHandler
)

val handler = object : ProgressHandler {
  override fun onProgress(percent: Double) {
      println("Progress: $percent%")
  }
  override fun onComplete(result: String) {
      println("Done: $result")
  }
  override fun onError(error: String) {
      println("Error: $error")
  }
}

download("https://example.com", handler)
```

**Java**

```java
public interface ProgressHandler {
  void onProgress(double percent);
  void onComplete(String result);
  void onError(String error);
}

static void download(
  String url, ProgressHandler handler)

download("https://example.com",
  new ProgressHandler() {
      public void onProgress(double percent) {
          System.out.println(percent + "%");
      }
      public void onComplete(String result) {
          System.out.println("Done: " + result);
      }
      public void onError(String error) {
          System.out.println("Error: " + error);
      }
  });
```

**C#**

```csharp
public interface ProgressHandler
{
  void OnProgress(double percent);
  void OnComplete(string result);
  void OnError(string error);
}

static void Download(
  string url, ProgressHandler handler)

public sealed class MyHandler : ProgressHandler
{
  public void OnProgress(double percent) {
      Console.WriteLine("Progress: " + percent + "%");
  }
  public void OnComplete(string result) {
      Console.WriteLine("Done: " + result);
  }
  public void OnError(string error) {
      Console.WriteLine("Error: " + error);
  }
}

MyLib.Download(
  "https://example.com",
  new MyHandler());
```

**TypeScript**

```typescript
interface ProgressHandler {
  onProgress(percent: number): void
  onComplete(result: string): void
  onError(error: string): void
}

function download(
  url: string,
  handler: ProgressHandler
): void

download("https://example.com", {
  onProgress(percent) {
      console.log(`Progress: ${percent}%`)
  },
  onComplete(result) {
      console.log(`Done: ${result}`)
  },
  onError(error) {
      console.log(`Error: ${error}`)
  }
})
```

**Python**

```python
def download(url: str, handler: object) -> None: ...

class MyHandler:
  def on_progress(self, percent: float) -> None:
      print(f"Progress: {percent}%")

  def on_complete(self, result: str) -> None:
      print(f"Done: {result}")

  def on_error(self, error: str) -> None:
      print(f"Error: {error}")

download("https://example.com", MyHandler())
```

### Ownership

Trait callbacks can be passed as `Box<dyn Trait>` or `Arc<dyn Trait>`. Use `Box` when the callback is used once or owned by a single call. Use `Arc` when the callback needs to be shared across multiple calls or stored for later use. The choice affects how the target language object is retained.

**Rust**

```rust
#[export]
pub fn single_use(handler: Box<dyn ProgressHandler>) {
  handler.on_complete("done");
}

#[export]
pub fn shared_use(handler: Arc<dyn ProgressHandler>) {
  let h = handler.clone();
  // can store h for later, pass to threads, etc.
  handler.on_progress(50.0);
}
```

**Swift**

```swift
// Both accept the same protocol
func singleUse(handler: ProgressHandler)
func sharedUse(handler: ProgressHandler)

singleUse(handler: MyHandler())
sharedUse(handler: MyHandler())
```

**Kotlin**

```kotlin
// Both accept the same interface
fun singleUse(handler: ProgressHandler)
fun sharedUse(handler: ProgressHandler)

singleUse(MyHandler())
sharedUse(MyHandler())
```

**Java**

```java
// Both accept the same interface
static void singleUse(ProgressHandler handler)
static void sharedUse(ProgressHandler handler)

singleUse(myHandler);
sharedUse(myHandler);
```

**C#**

```csharp
// Both accept the same interface
static void SingleUse(ProgressHandler handler)
static void SharedUse(ProgressHandler handler)

MyLib.SingleUse(new MyHandler());
MyLib.SharedUse(new MyHandler());
```

**TypeScript**

```typescript
// Both accept the same interface
function singleUse(handler: ProgressHandler): void
function sharedUse(handler: ProgressHandler): void

singleUse(handler)
sharedUse(handler)
```

**Python**

```python
def single_use(handler: object) -> None: ...
def shared_use(handler: object) -> None: ...

handler = MyHandler()
single_use(handler)
shared_use(handler)
```

### Async Methods

Trait methods can be async. Mark the trait with `#[async_trait]` and use `async fn` for methods that need to perform async work on the target language side. When Rust calls an async method, it awaits the result before continuing.

**Rust**

```rust
use async_trait::async_trait;

#[async_trait]
#[export]
pub trait DataProvider {
  async fn fetch(&self, key: &str) -> String;
}

#[export]
pub async fn process(
  provider: Box<dyn DataProvider>
) -> String {
  let data = provider.fetch("config").await;
  format!("Got: {}", data)
}
```

**Swift**

```swift
protocol DataProvider {
  func fetch(key: String) async -> String
}

func process(
  provider: DataProvider
) async -> String

class MyProvider: DataProvider {
  func fetch(key: String) async -> String {
      return "value for \(key)"
  }
}

let result = await process(
  provider: MyProvider())
```

**Kotlin**

```kotlin
interface DataProvider {
  suspend fun fetch(key: String): String
}

suspend fun process(
  provider: DataProvider
): String

class MyProvider : DataProvider {
  override suspend fun fetch(
      key: String
  ): String {
      return "value for $key"
  }
}

val result = process(MyProvider())
```

**Java**

```java
public interface DataProvider {
  // Java 8+
  CompletableFuture<String> fetch(
      String key);
}

// Java 21+ (virtual threads)
static String process(DataProvider provider)

// Java 8+ (CompletableFuture)
static CompletableFuture<String> process(
  DataProvider provider)

String result = process(key ->
  CompletableFuture.completedFuture(
      "value for " + key));
```

**C#**

```csharp
public interface DataProvider
{
  Task<string> Fetch(string key);
}

static Task<string> Process(
  DataProvider provider)

public sealed class MyProvider : DataProvider
{
  public Task<string> Fetch(string key) =>
      Task.FromResult("value for " + key);
}

string result =
  await MyLib.Process(new MyProvider());
```

**TypeScript**

```typescript
interface DataProvider {
  fetch(key: string): Promise<string>
}

async function process(
  provider: DataProvider
): Promise<string>

const result = await process({
  async fetch(key) {
      return `value for ${key}`
  }
})
```

**Python**

```python
async def process(provider: object) -> str: ...

class MyProvider:
  def fetch(self, key: str) -> str:
      return f"value for {key}"

result = await process(MyProvider())
```

### Storing Traits

Trait objects can be stored in class fields for later use. The target language object remains alive as long as the Rust class holds the reference.

**Rust**

```rust
pub struct Engine {
  logger: Box<dyn Logger>,
}

#[export]
impl Engine {
  pub fn new(logger: Box<dyn Logger>) -> Self {
      Engine { logger }
  }
  
  pub fn do_work(&self) {
      self.logger.log("Working...");
  }
}
```

**Swift**

```swift
public class Engine {
  public init(logger: Logger)
  public func doWork()
}

let engine = Engine(logger: MyLogger())
engine.doWork()
```

**Kotlin**

```kotlin
class Engine(logger: Logger) {
  fun doWork()
}

val engine = Engine(MyLogger())
engine.doWork()
```

**Java**

```java
public final class Engine
  implements AutoCloseable {
  public Engine(Logger logger)
  public void doWork()
}

Engine engine = new Engine(myLogger);
engine.doWork();
engine.close();
```

**C#**

```csharp
public interface Logger
{
  void Log(string message);
}

public sealed class Engine : IDisposable
{
  public Engine(Logger logger)
  public void DoWork()
}

using Engine engine = new Engine(myLogger);
engine.DoWork();
```

**TypeScript**

```typescript
interface Logger {
  log(message: string): void
}

class Engine {
  static new(logger: Logger): Engine
  doWork(): void
  dispose(): void
}

const engine = Engine.new({
  log(message) { console.log(message) }
})
engine.doWork()
engine.dispose()
```

**Python**

```python
class Engine:
  def __init__(self, logger: object) -> None: ...
  def do_work(self) -> None: ...

class MyLogger:
  def log(self, message: str) -> None:
      print(message)

engine = Engine(MyLogger())
engine.do_work()
```

### Thread Safety

For traits that will be called from multiple threads, add `Send + Sync` bounds. This ensures the target language implementation can be safely shared across threads.

```rust
#[export]
pub trait ThreadSafeLogger: Send + Sync {
    fn log(&self, message: &str);
}
```

### Limitations

Trait callbacks have some restrictions:

- Generic traits are not supported
- Associated types are not supported
- Default method implementations are ignored

## How It Works

Closures are passed as function pointers with an associated context. When the target language calls a function that accepts a closure, it packages the lambda and any captured state into a handle. Rust receives this handle and invokes the closure through a generated wrapper that unpacks arguments, calls the target language function, and returns the result.

Trait callbacks use a vtable mechanism. When the target language passes a callback object, BoltFFI creates a handle that pairs the object reference with a vtable of function pointers. Each method in the trait has a corresponding entry in the vtable. When Rust calls a method, it invokes the function pointer with the handle, which dispatches to the target language implementation. The handle tracks ownership so the object stays alive as long as Rust holds a reference.

---

Source: https://boltffi.dev/docs/streaming

# Streaming

Streams provide continuous data flow from Rust to target languages. Unlike async functions that return a single value, streams deliver multiple values over time. BoltFFI generates native stream types that integrate with each platform's concurrency model, and handles the underlying buffering and synchronization.

## The ffi\_stream Attribute

Mark a method with `#[ffi_stream]` to expose it as a stream. The attribute requires an `item` parameter specifying the type of data that flows through the stream, and accepts an optional `mode` parameter to control how consumers receive events.

```rust
#[ffi_stream(item = Reading)]                    // item type is Reading, async mode (default)
#[ffi_stream(item = Reading, mode = "async")]    // explicit async mode
#[ffi_stream(item = Reading, mode = "callback")] // callback mode
#[ffi_stream(item = Reading, mode = "batch")]    // batch mode
```

The `item` type must be a type that can cross the FFI boundary - primitives, records marked with `#[data]`, or other supported types. The method must return `Arc<EventSubscription<T>>` where `T` matches the item type.

## Stream Modes

BoltFFI supports three consumption modes. Each mode generates different bindings suited to different use cases.

### Async Mode

The default mode. Generated bindings use:

- Swift: `AsyncStream`
- Kotlin: `Flow`
- Java: callback-driven `StreamSubscription<T>`
- C#: `IAsyncEnumerable<T>`

Cancellation propagates automatically.

**Rust**

```rust
#[ffi_stream(item = Reading)]
pub fn readings(&self) -> Arc<EventSubscription<Reading>> {
  Arc::clone(&self.subscription)
}
```

**Swift**

```swift
public func readings() -> AsyncStream<Reading>

for await reading in sensor.readings() {
  print("Value: \(reading.value)")
}
```

**Kotlin**

```kotlin
fun readings(): Flow<Reading>

sensor.readings().collect { reading ->
  println("Value: ${reading.value}")
}
```

**Java**

```java
public StreamSubscription<Reading> readings(
  java.util.function.Consumer<Reading> callback)

StreamSubscription<Reading> sub =
  sensor.readings(reading ->
      System.out.println(reading.value()));
// later
sub.close();
```

**C#**

```csharp
public IAsyncEnumerable<Reading> Readings(
  CancellationToken cancellationToken = default)

await foreach (Reading reading in
  sensor.Readings(cancellationToken)) {
  Console.WriteLine(reading.Value);
}
```

**Python**

```python
subscription = sensor.readings()

while True:
  status = subscription.wait(50)
  if status < 0:
      break
  if status == 0:
      continue
  for reading in subscription.pop_batch():
      print(f"Value: {reading.value}")
```

### Callback Mode

Generates a method that takes a callback and returns a cancellable handle on targets with callback stream support. C# currently exposes stream methods as `IAsyncEnumerable<T>`, so consumers use `await foreach` and cancel with a `CancellationToken` or by breaking the loop.

**Rust**

```rust
#[ffi_stream(item = Reading, mode = "callback")]
pub fn readings(&self) -> Arc<EventSubscription<Reading>> {
  Arc::clone(&self.subscription)
}
```

**Swift**

```swift
public func readings(
  callback: @escaping (Reading) -> Void
) -> StreamSubscription<Reading>

let sub = sensor.readings { reading in
  print("Value: \(reading.value)")
}
// later
sub.cancel()
```

**Kotlin**

```kotlin
fun readings(
  callback: (Reading) -> Unit
): StreamSubscription<Reading>

val sub = sensor.readings { reading ->
  println("Value: ${reading.value}")
}
// later
sub.cancel()
```

**Java**

```java
public StreamSubscription<Reading> readings(
  java.util.function.Consumer<Reading> callback)

StreamSubscription<Reading> sub =
  sensor.readings(reading ->
      System.out.println(reading.value()));
// later
sub.close();
```

**C#**

```csharp
public IAsyncEnumerable<Reading> Readings(
  CancellationToken cancellationToken = default)

await foreach (Reading reading in
  sensor.Readings(cancellationToken)) {
  Console.WriteLine(reading.Value);
}
```

**Python**

```python
subscription = sensor.readings()

while True:
  status = subscription.wait(50)
  if status < 0:
      break
  if status == 0:
      continue
  for reading in subscription.pop_batch():
      print(f"Value: {reading.value}")
```

### Batch Mode

Generates a subscription object that lets consumers pull batches of events on their own schedule on targets with batch stream support. C# hides the batch polling behind `IAsyncEnumerable<T>`.

**Rust**

```rust
#[ffi_stream(item = Reading, mode = "batch")]
pub fn readings(&self) -> Arc<EventSubscription<Reading>> {
  Arc::clone(&self.subscription)
}
```

**Swift**

```swift
public func readings()
  -> StreamSubscription<Reading>

let sub = sensor.readings()
let batch = sub.popBatch(maxCount: 100)
for reading in batch {
  process(reading)
}
```

**Kotlin**

```kotlin
fun readings(): StreamSubscription<Reading>

val sub = sensor.readings()
val batch = sub.popBatch(maxCount = 100)
batch.forEach { reading -> process(reading) }
```

**Java**

```java
public StreamSubscription<Reading> readings()

StreamSubscription<Reading> sub =
  sensor.readings();
java.util.List<Reading> batch =
  sub.popBatch(100);
for (Reading reading : batch) {
  process(reading);
}
sub.close();
```

**C#**

```csharp
public IAsyncEnumerable<Reading> Readings(
  CancellationToken cancellationToken = default)

await foreach (Reading reading in
  sensor.Readings(cancellationToken)) {
  Process(reading);
}
```

**Python**

```python
subscription = sensor.readings()
batch = subscription.pop_batch(max_count=100)

for reading in batch:
  process(reading)
```

## Creating Streams

Streams are created using `EventSubscription` or `StreamProducer`. The choice depends on whether you need single or multiple subscribers.

### EventSubscription

`EventSubscription` creates an independent subscription per call. Each subscriber gets its own buffer and receives all events pushed after subscribing.

**Rust**

```rust
use boltffi::EventSubscription;
use std::sync::Arc;

pub struct Sensor {
  subscription: Arc<EventSubscription<Reading>>,
}

#[export]
impl Sensor {
  pub fn new() -> Self {
      Sensor {
          subscription: Arc::new(
              EventSubscription::new(256)
          ),
      }
  }
  
  #[ffi_stream(item = Reading)]
  pub fn readings(&self)
      -> Arc<EventSubscription<Reading>>
  {
      Arc::clone(&self.subscription)
  }
  
  pub fn emit(&self, value: f64) {
      self.subscription.push_event(Reading {
          value,
          timestamp: current_time_ms(),
      });
  }
}
```

**Swift**

```swift
public class Sensor {
  public init()
  public func readings() -> AsyncStream<Reading>
}

let sensor = Sensor()
for await reading in sensor.readings() {
  print(reading.value)
}
```

**Kotlin**

```kotlin
class Sensor {
  fun readings(): Flow<Reading>
}

val sensor = Sensor()
sensor.readings().collect { reading ->
  println(reading.value)
}
```

**Java**

```java
public final class Sensor
  implements AutoCloseable {
  public Sensor()
  public StreamSubscription<Reading> readings(
      java.util.function.Consumer<Reading> cb)
}

Sensor sensor = new Sensor();
StreamSubscription<Reading> sub =
  sensor.readings(reading ->
      System.out.println(reading.value()));
// later
sub.close();
sensor.close();
```

**C#**

```csharp
public sealed class Sensor : IDisposable {
  public Sensor()
  public IAsyncEnumerable<Reading> Readings(
      CancellationToken cancellationToken = default)
}

using Sensor sensor = new Sensor();
await foreach (Reading reading in sensor.Readings()) {
  Console.WriteLine(reading.Value);
}
```

**Python**

```python
sensor = Sensor()
subscription = sensor.readings()

if subscription.wait(50) > 0:
  for reading in subscription.pop_batch():
      print(reading.value)
```

### StreamProducer

`StreamProducer` broadcasts events to multiple subscribers. Each subscriber gets its own buffer, and pushing an event delivers it to all active subscribers.

**Rust**

```rust
use boltffi::StreamProducer;

pub struct EventBus {
  producer: StreamProducer<Event>,
}

#[export]
impl EventBus {
  pub fn new() -> Self {
      EventBus {
          producer: StreamProducer::new(256),
      }
  }
  
  #[ffi_stream(item = Event)]
  pub fn events(&self)
      -> Arc<EventSubscription<Event>>
  {
      self.producer.subscribe()
  }
  
  pub fn emit(&self, event: Event) {
      self.producer.push(event);
  }
}
```

**Swift**

```swift
let bus = EventBus()

Task {
  for await event in bus.events() {
      print("Sub 1: \(event)")
  }
}

Task {
  for await event in bus.events() {
      print("Sub 2: \(event)")
  }
}
```

**Kotlin**

```kotlin
val bus = EventBus()

scope.launch {
  bus.events().collect { event ->
      println("Sub 1: $event")
  }
}

scope.launch {
  bus.events().collect { event ->
      println("Sub 2: $event")
  }
}
```

**Java**

```java
EventBus bus = new EventBus();

StreamSubscription<Event> sub1 =
  bus.events(event ->
      System.out.println("Sub 1: " + event));

StreamSubscription<Event> sub2 =
  bus.events(event ->
      System.out.println("Sub 2: " + event));

// later
sub1.close();
sub2.close();
bus.close();
```

**C#**

```csharp
using EventBus bus = new EventBus();

await foreach (Event ev in bus.Events()) {
  Console.WriteLine("Sub 1: " + ev);
}

await foreach (Event ev in bus.Events()) {
  Console.WriteLine("Sub 2: " + ev);
}
```

**Python**

```python
bus = EventBus()

sub_1 = bus.events()
sub_2 = bus.events()

if sub_1.wait(50) > 0:
  for event in sub_1.pop_batch():
      print(f"Sub 1: {event}")

if sub_2.wait(50) > 0:
  for event in sub_2.pop_batch():
      print(f"Sub 2: {event}")
```

## Buffer Capacity

Each subscription has a ring buffer that holds events until the consumer processes them. The default capacity is 256 items. For high-frequency streams, increase the capacity to avoid dropping events when the consumer falls behind.

```rust
// Default capacity (256)
EventSubscription::new(256)

// Larger buffer for high-frequency data
EventSubscription::new(4096)

// StreamProducer with custom capacity
StreamProducer::new(4096)
```

When the buffer is full, new events are dropped. The producer continues without blocking.

## Stopping Streams

Streams can be stopped from either side. The producer can complete the stream, or the consumer can cancel their subscription.

### Producer-side completion

Call `unsubscribe()` on the subscription to signal that no more events will be sent. Active consumers receive a completion signal.

```rust
impl Sensor {
    pub fn stop(&self) {
        self.subscription.unsubscribe();
    }
}
```

### Consumer-side cancellation

In async mode, cancelling the task or breaking out of the loop cancels the subscription. In callback mode, call `cancel()` on the returned handle.

**Swift**

```swift
// Async mode - cancel the task
let task = Task {
  for await reading in sensor.readings() {
      if reading.value > threshold {
          break
      }
  }
}
task.cancel()

// Callback mode
let sub = sensor.readings { reading in
  process(reading)
}
sub.cancel()
```

**Kotlin**

```kotlin
// Async mode - cancel the job
val job = scope.launch {
  sensor.readings().collect { reading ->
      if (reading.value > threshold) {
          cancel()
      }
  }
}
job.cancel()

// Callback mode
val sub = sensor.readings { reading ->
  process(reading)
}
sub.cancel()
```

**Java**

```java
// Async/callback mode
StreamSubscription<Reading> sub =
  sensor.readings(reading ->
      process(reading));
sub.close();

// Batch mode
StreamSubscription<Reading> sub =
  sensor.readings();
sub.popBatch(100);
sub.close();
```

**C#**

```csharp
using var cts = new CancellationTokenSource();

await foreach (Reading reading in
  sensor.Readings(cts.Token)) {
  if (reading.Value > threshold) {
      break;
  }
}

cts.Cancel();
```

**Python**

```python
subscription = sensor.readings()

if subscription.wait(50) > 0:
  for reading in subscription.pop_batch():
      if reading.value > threshold:
          break

subscription.unsubscribe()
```

## How It Works

Streams use a continuation-based polling mechanism similar to async functions. Each subscription has a lock-free ring buffer for events and a scheduler that coordinates between the producer and consumer. When events are pushed, the scheduler wakes any parked continuation. The consumer polls the subscription, and when events are available, they're delivered in batches for efficiency. The entire hot path is lock-free, using atomic operations for state management.

---

Source: https://boltffi.dev/docs/errors

# Errors

Rust represents fallible operations with `Result<T, E>`. Target languages have different conventions: Swift uses `throws`, Kotlin, Java, and C# use exceptions, and TypeScript uses try/catch. BoltFFI converts `Result` return types into the native error handling mechanism of each platform. When a function returns `Err`, it becomes a thrown error or exception in the target language.

## Supported Error Types

The error type in `Result<T, E>` can be:

- `String` or `&'static str` - becomes a generic error with a message
- A struct marked with `#[error]` - becomes a structured error type
- An enum marked with `#[error]` - becomes an error enum

The `#[error]` attribute marks types as error types. Generated bindings expose them through each target's native error model:

- Swift: error types conform to `Error`
- Kotlin: error types extend `Exception`
- Java: error enums and classes include a nested `Exception` type that extends `RuntimeException`
- C#: generated methods throw `BoltException` for string errors or typed `*Exception` wrappers that expose the original error value

## String Errors

String errors use generic wrapper types:

- Swift: `FfiError`
- Kotlin: `FfiException`
- Java: `RuntimeException`
- C#: `BoltException`
- TypeScript: `FfiException`

Custom error types are thrown directly or wrapped as the native target requires.

The simplest approach is returning `Result<T, String>` or `Result<T, &'static str>`. The error message is captured in a generic error type.

**Rust**

```rust
#[export]
pub fn parse_int(s: &str) -> Result<i32, String> {
  s.parse()
      .map_err(|e| format!("parse failed: {}", e))
}
```

**Swift**

```swift
public struct FfiError: Error {
  public let message: String
}

func parseInt(s: String) throws -> Int32

do {
  let n = try parseInt(s: "42")
} catch let error as FfiError {
  print(error.message)
}
```

**Kotlin**

```kotlin
class FfiException(
  val code: Int,
  message: String
) : Exception(message)

@Throws(FfiException::class)
fun parseInt(s: String): Int

try {
  val n = parseInt("42")
} catch (e: FfiException) {
  println(e.message)
}
```

**Java**

```java
// String errors become RuntimeException
static int parseInt(String s)

try {
  int n = parseInt("42");
} catch (RuntimeException e) {
  System.out.println(e.getMessage());
}
```

**C#**

```csharp
// String errors become BoltException
static int ParseInt(string s)

try {
  int n = MyLib.ParseInt("42");
} catch (BoltException e) {
  Console.WriteLine(e.Message);
}
```

**TypeScript**

```typescript
class FfiException extends Error {
  readonly message: string
}

function parseInt(s: string): number

try {
  const n = parseInt("42")
} catch (e) {
  if (e instanceof FfiException) {
      console.log(e.message)
  }
}
```

**Python**

```python
def parse_int(s: str) -> int: ...

try:
  n = parse_int("42")
except RuntimeError as error:
  print(error)
```

## Struct Errors

For structured error information, define a struct with `#[error]`. The struct becomes a throwable error type in both languages.

**Rust**

```rust
#[error]
pub struct ParseError {
  pub line: u32,
  pub column: u32,
  pub message: String,
}

#[export]
pub fn parse_config(
  input: &str
) -> Result<Config, ParseError> {
  Err(ParseError {
      line: 10,
      column: 5,
      message: "unexpected token".into(),
  })
}
```

**Swift**

```swift
public struct ParseError: Error {
  public let line: UInt32
  public let column: UInt32
  public let message: String
}

func parseConfig(input: String) throws -> Config

do {
  let config = try parseConfig(input: src)
} catch let error as ParseError {
  print("\(error.line):\(error.column): \(error.message)")
}
```

**Kotlin**

```kotlin
data class ParseError(
  val line: UInt,
  val column: UInt,
  val message: String
) : Exception()

@Throws(ParseError::class)
fun parseConfig(input: String): Config

try {
  val config = parseConfig(src)
} catch (e: ParseError) {
  println("${e.line}:${e.column}: ${e.message}")
}
```

**Java**

```java
// Java 16+
public record ParseError(
  int line, int column, String message) {
  public static final class Exception
      extends RuntimeException {
      public ParseError getError()
  }
}

// Java 8+
public final class ParseError {
  public final int line;
  public final int column;
  public final String message;
  public static final class Exception
      extends RuntimeException {
      public ParseError getError()
  }
}

static Config parseConfig(String input)

try {
  Config config = parseConfig(src);
} catch (ParseError.Exception e) {
  ParseError err = e.getError();
  System.out.println(
      err.line() + ":" + err.column()
      + ": " + err.message());
}
```

**C#**

```csharp
public readonly record struct ParseError(
  uint Line,
  uint Column,
  string Message
);

public sealed class ParseErrorException : Exception
{
  public ParseError Error { get; }
}

static Config ParseConfig(string input)

try {
  Config config = MyLib.ParseConfig(src);
} catch (ParseErrorException e) {
  ParseError err = e.Error;
  Console.WriteLine(
      err.Line + ":" + err.Column + ": "
      + err.Message);
}
```

**TypeScript**

```typescript
interface ParseError {
  readonly line: number
  readonly column: number
  readonly message: string
}

class ParseErrorException extends Error {
  readonly error: ParseError
}

function parseConfig(input: string): Config

try {
  const config = parseConfig(src)
} catch (e) {
  if (e instanceof ParseErrorException) {
      console.log(`${e.error.line}:${e.error.column}: ${e.error.message}`)
  }
}
```

**Python**

```python
@dataclass(frozen=True, slots=True)
class ParseError:
  line: int
  column: int
  message: str

class ParseErrorException(RuntimeError):
  error: ParseError

def parse_config(input: str) -> Config: ...

try:
  config = parse_config(src)
except ParseErrorException as error:
  details = error.error
  print(f"{details.line}:{details.column}: {details.message}")
```

## Enum Errors

Error enums let you represent distinct failure cases. Simple enums (no associated data) become native enums. Enums with payloads become sealed types.

### Simple Enums

**Rust**

```rust
#[error]
pub enum AuthError {
  InvalidCredentials,
  SessionExpired,
  AccountLocked,
}

#[export]
pub fn login(
  user: &str, pass: &str
) -> Result<Session, AuthError> {
  Err(AuthError::InvalidCredentials)
}
```

**Swift**

```swift
public enum AuthError: Error {
  case invalidCredentials
  case sessionExpired
  case accountLocked
}

func login(user: String, pass: String) throws -> Session

do {
  let session = try login(user: u, pass: p)
} catch let error as AuthError {
  switch error {
  case .invalidCredentials:
      print("Wrong username or password")
  case .sessionExpired:
      print("Please log in again")
  case .accountLocked:
      print("Account is locked")
  }
}
```

**Kotlin**

```kotlin
enum class AuthError : Exception() {
  InvalidCredentials,
  SessionExpired,
  AccountLocked;
}

@Throws(AuthError::class)
fun login(user: String, pass: String): Session

try {
  val session = login(u, p)
} catch (e: AuthError) {
  when (e) {
      AuthError.InvalidCredentials ->
          println("Wrong username or password")
      AuthError.SessionExpired ->
          println("Please log in again")
      AuthError.AccountLocked ->
          println("Account is locked")
  }
}
```

**Java**

```java
public enum AuthError {
  INVALID_CREDENTIALS,
  SESSION_EXPIRED,
  ACCOUNT_LOCKED;

  public static final class Exception
      extends RuntimeException {
      public AuthError getError()
  }
}

static Session login(String user, String pass)

try {
  Session session = login(u, p);
} catch (AuthError.Exception e) {
  switch (e.getError()) {
      case INVALID_CREDENTIALS:
          System.out.println("Wrong credentials");
          break;
      case SESSION_EXPIRED:
          System.out.println("Please log in again");
          break;
      case ACCOUNT_LOCKED:
          System.out.println("Account is locked");
          break;
  }
}
```

**C#**

```csharp
public enum AuthError
{
  InvalidCredentials,
  SessionExpired,
  AccountLocked,
}

public sealed class AuthErrorException : Exception
{
  public AuthError Error { get; }
}

static Session Login(string user, string pass)

try {
  Session session = MyLib.Login(u, p);
} catch (AuthErrorException e) {
  switch (e.Error) {
      case AuthError.InvalidCredentials:
          Console.WriteLine("Wrong credentials");
          break;
      case AuthError.SessionExpired:
          Console.WriteLine("Please log in again");
          break;
      case AuthError.AccountLocked:
          Console.WriteLine("Account is locked");
          break;
  }
}
```

**TypeScript**

```typescript
enum AuthError {
  InvalidCredentials = 0,
  SessionExpired = 1,
  AccountLocked = 2
}

class AuthErrorException extends Error {
  readonly code: AuthError
}

function login(user: string, pass: string): Session

try {
  const session = login(u, p)
} catch (e) {
  if (e instanceof AuthErrorException) {
      switch (e.code) {
          case AuthError.InvalidCredentials:
              console.log("Wrong username or password")
              break
          case AuthError.SessionExpired:
              console.log("Please log in again")
              break
          case AuthError.AccountLocked:
              console.log("Account is locked")
              break
      }
  }
}
```

**Python**

```python
from enum import IntEnum

class AuthError(IntEnum):
  INVALID_CREDENTIALS = 0
  SESSION_EXPIRED = 1
  ACCOUNT_LOCKED = 2

class AuthErrorException(RuntimeError):
  error: AuthError

def login(user: str, pass_: str) -> Session: ...

try:
  session = login(u, p)
except AuthErrorException as error:
  match error.error:
      case AuthError.INVALID_CREDENTIALS:
          print("Wrong username or password")
      case AuthError.SESSION_EXPIRED:
          print("Please log in again")
      case AuthError.ACCOUNT_LOCKED:
          print("Account is locked")
```

### Enums with Payloads

When enum variants carry associated data, the error becomes a sealed type hierarchy.

**Rust**

```rust
#[error]
pub enum ApiError {
  Network { message: String },
  NotFound,
  RateLimited { retry_after: u32 },
}

#[export]
pub fn fetch_user(id: u64) -> Result<User, ApiError> {
  Err(ApiError::RateLimited { retry_after: 30 })
}
```

**Swift**

```swift
public enum ApiError: Error {
  case network(message: String)
  case notFound
  case rateLimited(retryAfter: UInt32)
}

func fetchUser(id: UInt64) throws -> User

do {
  let user = try fetchUser(id: 42)
} catch let error as ApiError {
  switch error {
  case .network(let message):
      print("Network error: \(message)")
  case .notFound:
      print("User not found")
  case .rateLimited(let seconds):
      print("Try again in \(seconds)s")
  }
}
```

**Kotlin**

```kotlin
sealed class ApiError : Exception() {
  data class Network(
      val message: String) : ApiError()
  data object NotFound : ApiError()
  data class RateLimited(
      val retryAfter: UInt) : ApiError()
}

@Throws(ApiError::class)
fun fetchUser(id: ULong): User

try {
  val user = fetchUser(42u)
} catch (e: ApiError) {
  when (e) {
      is ApiError.Network ->
          println("Network error: ${e.message}")
      is ApiError.NotFound ->
          println("User not found")
      is ApiError.RateLimited ->
          println("Try again in ${e.retryAfter}s")
  }
}
```

**Java**

```java
// Java 17+
public sealed interface ApiError {
  record Network(String message)
      implements ApiError {}
  record NotFound() implements ApiError {}
  record RateLimited(int retryAfter)
      implements ApiError {}

  public static final class Exception
      extends RuntimeException {
      public ApiError getError()
  }
}

// Java 8+
public abstract class ApiError {
  public static final class Network
      extends ApiError {
      public final String message;
  }
  public static final class NotFound
      extends ApiError {}
  public static final class RateLimited
      extends ApiError {
      public final int retryAfter;
  }

  public static final class Exception
      extends RuntimeException {
      public ApiError getError()
  }
}

static User fetchUser(long id)

try {
  User user = fetchUser(42L);
} catch (ApiError.Exception e) {
  ApiError err = e.getError();
  if (err instanceof ApiError.Network n)
      System.out.println(n.message());
  else if (err instanceof ApiError.RateLimited r)
      System.out.println(r.retryAfter());
}
```

**C#**

```csharp
public abstract record ApiError
{
  public sealed record Network(
      string Message) : ApiError;
  public sealed record NotFound : ApiError;
  public sealed record RateLimited(
      uint RetryAfter) : ApiError;
}

public sealed class ApiErrorException : Exception
{
  public ApiError Error { get; }
}

static User FetchUser(ulong id)

try {
  User user = MyLib.FetchUser(42);
} catch (ApiErrorException e) {
  if (e.Error is ApiError.Network n)
      Console.WriteLine(n.Message);
  else if (e.Error is ApiError.RateLimited r)
      Console.WriteLine(r.RetryAfter);
}
```

**TypeScript**

```typescript
type ApiError =
  | { readonly tag: "Network";
      readonly message: string }
  | { readonly tag: "NotFound" }
  | { readonly tag: "RateLimited";
      readonly retryAfter: number }

class ApiErrorException extends Error {
  readonly error: ApiError
}

function fetchUser(id: bigint): User

try {
  const user = fetchUser(42n)
} catch (e) {
  if (e instanceof ApiErrorException) {
      switch (e.error.tag) {
          case "Network":
              console.log(e.error.message)
              break
          case "NotFound":
              console.log("Not found")
              break
          case "RateLimited":
              console.log(e.error.retryAfter)
              break
      }
  }
}
```

**Python**

```python
class ApiError:
  pass

@dataclass(frozen=True, slots=True)
class ApiErrorNetwork(ApiError):
  message: str

@dataclass(frozen=True, slots=True)
class ApiErrorNotFound(ApiError):
  pass

@dataclass(frozen=True, slots=True)
class ApiErrorRateLimited(ApiError):
  retry_after: int

class ApiErrorException(RuntimeError):
  error: ApiError

def fetch_user(id: int) -> User: ...

try:
  user = fetch_user(42)
except ApiErrorException as error:
  details = error.error
  if isinstance(details, ApiErrorNetwork):
      print(f"Network error: {details.message}")
  elif isinstance(details, ApiErrorNotFound):
      print("User not found")
  elif isinstance(details, ApiErrorRateLimited):
      print(f"Try again in {details.retry_after}s")
```

## Async Errors

Async functions that return `Result` work the same way. The error is delivered through the target language's native error handling when the async operation completes.

**Rust**

```rust
#[export]
pub async fn fetch_data(
  url: &str
) -> Result<Vec<u8>, FetchError> {
  let response = client.get(url).await?;
  Ok(response.bytes().await?)
}
```

**Swift**

```swift
func fetchData(url: String) async throws -> Data

do {
  let data = try await fetchData(url: endpoint)
  process(data)
} catch let error as FetchError {
  handle(error)
}
```

**Kotlin**

```kotlin
@Throws(FetchError::class)
suspend fun fetchData(url: String): ByteArray

try {
  val data = fetchData(endpoint)
  process(data)
} catch (e: FetchError) {
  handle(e)
}
```

**Java**

```java
// Java 21+ (virtual threads)
static byte[] fetchData(String url)
  // throws FetchError.Exception

// Java 8+ (CompletableFuture)
static CompletableFuture<byte[]> fetchData(
  String url)

try {
  byte[] data = fetchData(endpoint);
  process(data);
} catch (FetchError.Exception e) {
  handle(e);
}
```

**C#**

```csharp
static Task<byte[]> FetchData(string url)

try {
  byte[] data = await MyLib.FetchData(endpoint);
  Process(data);
} catch (FetchErrorException e) {
  Handle(e);
}
```

**TypeScript**

```typescript
async function fetchData(
  url: string): Promise<Uint8Array>

try {
  const data = await fetchData(endpoint)
  process(data)
} catch (e) {
  if (e instanceof FetchErrorException) {
      handle(e)
  }
}
```

**Python**

```python
class FetchError:
  pass

class FetchErrorException(RuntimeError):
  error: FetchError

async def fetch_data(url: str) -> bytes: ...

try:
  data = await fetch_data(endpoint)
  process(data)
except FetchErrorException as error:
  handle(error)
```

---

Source: https://boltffi.dev/docs/custom-types

# Custom Types

Sometimes you need to expose types from external crates that you don't own. You can't add `#[data]` to a type defined in another crate, but you can teach BoltFFI how to convert it to and from an FFI-compatible representation. This lets you use types from popular crates like `chrono`, `uuid`, or `url` directly in your exported API.

The conversion happens automatically at the FFI boundary. Your Rust code uses the original type, consumers see either a primitive or a record, and BoltFFI handles the conversion in both directions.

## The custom\_type! Macro

The `custom_type!` macro defines a conversion between an external type and an FFI-compatible representation. BoltFFI uses this mapping whenever the type appears in function signatures, struct fields, or return values.

```rust
use boltffi::custom_type;

custom_type! {
    pub Uuid,
    remote = uuid::Uuid,
    repr = String,
    into_ffi = |uuid| uuid.to_string(),
    try_from_ffi = |s| uuid::Uuid::parse_str(&s).map_err(|_| boltffi::CustomTypeConversionError),
}
```

The macro takes these parameters:

- **name** - The identifier BoltFFI uses to track this mapping. Must be unique within your crate.
- **remote** - The external type you're wrapping. This is what your Rust code uses.
- **repr** - The FFI-compatible representation. Must be a type BoltFFI already knows how to transfer: primitives, `String`, `Vec<T>`, or types marked with `#[data]`.
- **into\_ffi** - A closure that converts from the remote type to the repr. Takes a reference to the remote type.
- **try\_from\_ffi** - A closure that converts from the repr back to the remote type. Returns `Result<Remote, Error>`.
- **error** - (optional) The error type returned by `try_from_ffi`. Defaults to `CustomTypeConversionError`.

After defining this, you can use `uuid::Uuid` directly in your API:

```rust
#[export]
impl UserService {
    pub fn get_user(&self, id: uuid::Uuid) -> Option<User> {
        self.users.get(&id).cloned()
    }
    
    pub fn create_user(&self, name: String) -> uuid::Uuid {
        let id = uuid::Uuid::new_v4();
        self.users.insert(id, User { id, name });
        id
    }
}
```

Consumers see `String` in the generated bindings. The conversion is transparent.

## The CustomFfiConvertible Trait

For types you define yourself, you can implement the `CustomFfiConvertible` trait directly and mark the impl with `#[custom_ffi]`. This gives you access to the full type system and works for types that need complex conversion logic.

```rust
use boltffi::{custom_ffi, CustomFfiConvertible};

pub struct UserId(i64);

#[custom_ffi]
impl CustomFfiConvertible for UserId {
    type FfiRepr = i64;
    type Error = boltffi::CustomTypeConversionError;
    
    fn into_ffi(&self) -> Self::FfiRepr {
        self.0
    }
    
    fn try_from_ffi(repr: Self::FfiRepr) -> Result<Self, Self::Error> {
        if repr > 0 {
            Ok(UserId(repr))
        } else {
            Err(boltffi::CustomTypeConversionError)
        }
    }
}
```

The `#[custom_ffi]` attribute generates the wire encoding implementations automatically. The trait requires:

- **FfiRepr** - The FFI-compatible representation type.
- **Error** - The error type for failed conversions.
- **into\_ffi** - Converts from the custom type to the repr.
- **try\_from\_ffi** - Converts from the repr to the custom type.

Note: Due to Rust's orphan rule, you cannot implement `CustomFfiConvertible` for types from external crates. Use the `custom_type!` macro for those.

## Choosing an Approach

Use `custom_type!` when:

- The type is from an external crate (chrono, uuid, url, etc.)
- The conversion is straightforward (parse/format, extract field)

Use `#[custom_ffi]` when:

- The type is defined in your crate
- You want the type to implement `WireEncode`/`WireDecode` directly
- You need validation logic in `try_from_ffi`

## Representation Types

The representation type must be something BoltFFI can transfer across the FFI boundary:

| Remote Type             | Good Repr                        | Why                    |
| ----------------------- | -------------------------------- | ---------------------- |
| `uuid::Uuid`            | `String`                         | Standard string format |
| `chrono::DateTime<Utc>` | `i64`                            | Unix timestamp millis  |
| `url::Url`              | `String`                         | URLs are strings       |
| `rust_decimal::Decimal` | `String`                         | Avoid floating point   |
| `geo::Point`            | `GeoPoint` (your `#[data]` type) | Structured data        |

For structured data, define a record type:

```rust
#[data]
pub struct GeoPoint {
    pub lat: f64,
    pub lng: f64,
}

custom_type! {
    pub GeoPointWrapper,
    remote = geo::Point,
    repr = GeoPoint,
    into_ffi = |point| GeoPoint { lat: point.y(), lng: point.x() },
    try_from_ffi = |gp| Ok(geo::Point::new(gp.lng, gp.lat)),
}
```

## Containers

Custom types work inside containers. If you define a conversion for `uuid::Uuid`, you can use `Vec<uuid::Uuid>`, `Option<uuid::Uuid>`, and `Result<uuid::Uuid, E>` in your API. BoltFFI applies the conversion to each element automatically.

```rust
#[export]
impl BatchService {
    pub fn get_users(&self, ids: Vec<uuid::Uuid>) -> Vec<User> {
        ids.iter()
            .filter_map(|id| self.users.get(id).cloned())
            .collect()
    }
}
```

The generated bindings show `[String]` for the parameter. Each string is converted to a `Uuid` before your code runs.

## Conversion Errors

When `try_from_ffi` returns an error, BoltFFI panics with a message identifying the custom type. This is intentional: invalid data crossing the FFI boundary indicates a bug in the consumer's code. If you need to handle invalid input gracefully, validate at the API level:

```rust
#[export]
impl UserService {
    pub fn get_user(&self, id: String) -> Result<Option<User>, ValidationError> {
        let uuid = uuid::Uuid::parse_str(&id)
            .map_err(|_| ValidationError::InvalidUuid)?;
        Ok(self.users.get(&uuid).cloned())
    }
}
```

---

Source: https://boltffi.dev/docs/packaging

# Packaging

Packaging takes your Rust library and produces artifacts ready for each packaged platform. A single command handles everything: compiling for each target architecture, generating bindings, and bundling the results into the format each platform expects.

`pack` is for targets where BoltFFI owns the platform package layout, such as an Apple
XCFramework/SwiftPM package, Android jniLibs, Java JNI output, a C# NuGet package, a WASM npm
package, or a Python wheel. `generate` writes only the source bindings for a single language and is
useful when you want to integrate them into an existing project layout yourself.

## Overview

The packaging process has three stages:

1. **Build** - Compile your Rust library for each target architecture (arm64, x86\_64, etc.)
2. **Generate** - Create Swift/Kotlin/Java/C#/TypeScript/Python bindings and C headers from your exported API
3. **Package** - Bundle everything into platform-specific formats

For Apple, this produces an xcframework and SwiftPM package. For Android, this produces jniLibs and Kotlin sources. For Java (JVM), this produces a JNI shared library and Java sources. For C#, this produces a NuGet package containing the bindings and native runtime assets. For WASM, this produces an npm package with TypeScript bindings. For Python, this produces a generated source package plus one or more wheels for the current host.

## Getting Started

Initialize your project with a configuration file:

```bash
boltffi init
```

This creates `boltffi.toml` with defaults. The minimal configuration is:

```toml
[package]
name = "mylib"
```

Your `Cargo.toml` needs the right crate types:

```toml
[lib]
crate-type = ["staticlib"]
```

## Packaging All Platforms

Package each platform with its own command:

```bash
boltffi pack apple
boltffi pack android
boltffi pack java
boltffi pack csharp
boltffi pack wasm
boltffi pack python
```

Add `--release` for optimized builds:

```bash
boltffi pack apple --release
boltffi pack android --release
boltffi pack java --release
boltffi pack csharp --release
boltffi pack wasm --release
boltffi pack python --release
```

Or run the full pipeline (check, build, generate, pack) for all platforms at once:

```bash
boltffi release all
```

The output lands in `dist/` with a subdirectory per platform:

```
dist/
├── apple/
│   ├── MyLib.xcframework/
│   │   ├── ios-arm64/
│   │   │   ├── Headers/
│   │   │   │   └── mylib.h
│   │   │   └── libmylib.a
│   │   ├── ios-arm64_x86_64-simulator/
│   │   │   ├── Headers/
│   │   │   │   └── mylib.h
│   │   │   └── libmylib.a
│   │   └── Info.plist
│   ├── Package.swift
│   └── Sources/
│       └── BoltFFI/
│           └── MyLibBoltFFI.swift
├── android/
│   ├── jniLibs/
│   │   ├── arm64-v8a/
│   │   │   └── libmylib.so
│   │   ├── armeabi-v7a/
│   │   │   └── libmylib.so
│   │   ├── x86/
│   │   │   └── libmylib.so
│   │   └── x86_64/
│   │       └── libmylib.so
│   └── kotlin/
│       └── com/example/mylib/
│           └── MyLib.kt
├── java/
│   ├── com/example/mylib/
│   │   ├── MyLib.java
│   │   ├── Native.java
│   │   └── ... (records, enums, etc.)
│   ├── jni/
│   │   ├── jni_glue.c
│   │   └── mylib.h
│   └── libmylib_jni.dylib (or .so)
├── csharp/
│   ├── BoltFFI.CSharp.csproj
│   ├── src/
│   │   ├── MyLib.cs
│   │   ├── Point.cs
│   │   └── ... (records, enums, classes, callbacks, streams)
│   ├── runtimes/
│   │   ├── osx-arm64/native/libmylib.dylib
│   │   ├── linux-x64/native/libmylib.so
│   │   ├── win-x64/native/mylib.dll
│   │   └── win-arm64/native/mylib.dll
│   └── packages/
│       └── MyLib.0.1.0.nupkg
├── wasm/
│   └── pkg/
│       ├── mylib_bg.wasm
│       ├── mylib.js
│       ├── mylib.d.ts
│       ├── bundler.js
│       ├── web.js
│       ├── node.js
│       └── package.json
└── python/
    ├── mylib/
    │   ├── __init__.py
    │   ├── __init__.pyi
    │   ├── _native.c
    │   ├── py.typed
    │   └── libmylib.dylib (or .so / .dll)
    ├── pyproject.toml
    ├── setup.py
    └── wheelhouse/
        └── mylib-0.1.0-...whl
```

## Step-by-Step Workflow

The `boltffi pack` command combines build, generate, and package steps. If you need more control, run each step separately:

**Apple:**

```bash
boltffi build apple --release
boltffi generate swift
boltffi pack apple --release --no-build
```

**Android:**

```bash
boltffi build android --release
boltffi generate kotlin
boltffi pack android --release --no-build
```

**Java:**

```bash
boltffi build java --release
boltffi generate java
boltffi pack java --release --no-build
```

**C#:**

```bash
boltffi generate csharp
boltffi pack csharp --release
```

`pack csharp` builds the Rust `cdylib` for each runtime identifier configured in
`targets.csharp.runtime_identifiers`, so it does not accept Cargo `--target` passthrough args.

**WASM:**

```bash
boltffi build wasm --release
boltffi generate typescript
boltffi pack wasm --release --no-build
```

**Python**

```bash
boltffi build --release
boltffi generate python
boltffi pack python --release --no-build
```

Use `--no-build` with `pack` when you've already built, to skip recompilation.

## Apple Packaging

Package everything for iOS and iOS Simulator:

```bash
boltffi pack apple --release
```

This builds for arm64 (device) and arm64/x86\_64 (simulator), generates Swift bindings and a C header, creates an xcframework, and generates a SwiftPM package.

Those slices come from `targets.apple.ios_architectures` and `targets.apple.simulator_architectures`. If you enable macOS, `targets.apple.macos_architectures` is included too.

If you enable `[targets.apple.debug_symbols]`, BoltFFI also writes `{XcframeworkName}.xcframework.symbols.zip` under `dist/apple/symbols/`. That companion archive contains the unstripped slice libraries plus a `symbols.json` manifest.

For release-style packaging, BoltFFI rejects this option unless the selected Cargo profile has debuginfo enabled, for example via `[profile.release] debug = true`.

### Output Structure

```
dist/apple/
├── MyLib.xcframework/
│   ├── ios-arm64/
│   │   ├── Headers/
│   │   │   └── mylib.h
│   │   └── libmylib.a
│   ├── ios-arm64_x86_64-simulator/
│   │   ├── Headers/
│   │   │   └── mylib.h
│   │   └── libmylib.a
│   └── Info.plist
├── Package.swift
└── Sources/
    └── BoltFFI/
        └── MyLibBoltFFI.swift
```

The xcframework contains fat libraries for each platform slice. The SwiftPM package ties it all together with a binary target for the xcframework and a Swift target for the generated bindings.

### Including macOS

To also build for macOS, add this to your `boltffi.toml`:

```toml
[targets.apple]
include_macos = true
```

Then run:

```bash
boltffi pack apple --release
```

The xcframework will include an additional `macos-arm64_x86_64` slice.

### Restricting Apple Slices

To package only a subset of Apple slices, configure them explicitly:

```toml
[targets.apple]
ios_architectures = ["arm64"]
simulator_architectures = ["arm64"]
include_macos = true
macos_architectures = ["arm64"]
```

`boltffi pack apple --no-build` validates exactly those configured slices. Stale artifacts for old
simulator or macOS architectures are ignored. Any Apple architecture list can be set to `[]` to
exclude that slice family, as long as at least one Apple slice remains enabled overall.

### SwiftPM Layouts

The `layout` option controls how the SwiftPM package is structured. Configure it in `boltffi.toml`:

```toml
[targets.apple.spm]
layout = "ffi-only"  # or "bundled" or "split"
```

**ffi-only** (default): Self-contained package with the xcframework and generated Swift. Import and use directly.

**bundled**: For existing Swift packages where you want to add generated bindings to your own wrapper target. Set `wrapper_sources` to your target's source directory.

**split**: Binary-only package. The generated Swift is written to a separate location for you to include in your own package. Use this when you need full control over the Swift target.

See [Configuration](/docs/configuration.md) for the full list of options.

### Using in Xcode

1. In Xcode, go to File → Add Package Dependencies
2. Click "Add Local..." and select the `dist/apple` directory
3. Add the package to your target

Then import and use:

```swift
import MyLib

let result = someExportedFunction()
```

The package exposes a single module with your library name. All exported functions, classes, and types are available.

### Remote Distribution

For distributing via GitHub releases or another host:

```toml
[targets.apple.spm]
distribution = "remote"
repo_url = "https://github.com/you/mylib/releases/download"
```

```bash
boltffi pack apple --release --version 1.0.0
```

This generates a `Package.swift` that points to a remote zip URL instead of a local path. Upload `MyLib.xcframework.zip` to your release, and consumers can add your package by URL.

## Android Packaging

Package everything for Android:

```bash
boltffi pack android --release
```

This builds for all Android ABIs (arm64-v8a, armeabi-v7a, x86, x86\_64), generates Kotlin bindings and JNI glue, and copies the shared libraries to jniLibs.

If you enable `[targets.android.debug_symbols]`, BoltFFI also writes `{crate_artifact_name}.android.symbols.zip` under `dist/android/symbols/`. That archive mirrors the `jniLibs/<abi>/` layout and includes a `symbols.json` manifest.

For release-style packaging, BoltFFI rejects this option unless the selected Cargo profile has debuginfo enabled.

### Output Structure

```
dist/android/
├── jniLibs/
│   ├── arm64-v8a/
│   │   └── libmylib.so
│   ├── armeabi-v7a/
│   │   └── libmylib.so
│   ├── x86/
│   │   └── libmylib.so
│   └── x86_64/
│       └── libmylib.so
└── kotlin/
    ├── com/
    │   └── example/
    │       └── mylib/
    │           └── MyLib.kt
    └── jni/
        └── jni_glue.c
```

The jniLibs folder follows the standard Android layout. Each ABI gets its own shared library. The Kotlin sources include your bindings and the JNI glue that connects them to the native code.

### Using in Android Studio

1. Copy `dist/android/jniLibs` to your app module's `src/main/` directory
2. Copy the Kotlin sources from `dist/android/kotlin/com/...` to your source set
3. Add the JNI glue to your native build (if using CMake or ndk-build)

Then import and use:

```kotlin
import com.example.mylib.*

val result = someExportedFunction()
```

### Gradle Integration

If you're using the Android Gradle Plugin with native support, point it at the jniLibs:

```kotlin
android {
    sourceSets {
        getByName("main") {
            jniLibs.srcDirs("src/main/jniLibs")
        }
    }
}
```

The shared libraries are loaded automatically when you first call into your Kotlin bindings.

## Kotlin Multiplatform Packaging

Package the experimental Kotlin Multiplatform module:

```bash
boltffi pack kmp --experimental --release
```

This generates the KMP sources, builds Android `jniLibs`, and writes JVM desktop native resources to `dist/kotlin-multiplatform/src/jvmMain/resources/native/<host-target>/`.

`pack kmp` verifies `boltffi-kmp-support.json` before packaging. The report must match the current KMP package, module, min SDK, selected platforms, and pruning policy. A module generated with `preview_prune_unsupported = true` cannot be packaged under strict config.

KMP reuses the Java JVM host matrix:

```toml
[targets.kotlin_multiplatform]
enabled = true

[targets.java.jvm]
host_targets = ["current", "linux-x86_64"]
```

`targets.java.jvm.enabled` does not need to be true for KMP packaging to read `host_targets`. The default is `["current"]`.

## Java Packaging

Package for JVM:

```bash
boltffi pack java --release
```

This builds the host Rust library, generates Java bindings and a C header, links the JNI glue against the Rust `staticlib` when available, and writes the native output to `dist/java/native/<host-target>/`. A flat current-host `_jni` copy is kept in `dist/java/` for compatibility during the transition.

If you enable `[targets.java.jvm.debug_symbols]`, BoltFFI also writes `{selected_artifact_name}.jvm.symbols.zip` under `dist/java/symbols/`. That archive mirrors `native/<host-target>/` and includes a `symbols.json` manifest for host-target mapping.

For release-style packaging, BoltFFI rejects this option unless the selected Cargo profile has debuginfo enabled. On Windows MSVC hosts, BoltFFI also archives `.pdb` sidecars for the packaged JNI DLL and bundled Rust DLL when present.

### Prerequisites

- JDK 8+ installed
- `JAVA_HOME` environment variable set
- `clang` available (used to compile the JNI bridge)

### Configuration

Enable Java in `boltffi.toml`:

```toml
[targets.java]
min_version = 8  # or 16, 17, 21

[targets.java.jvm]
enabled = true
host_targets = ["current", "linux-x86_64"]
```

`host_targets` defaults to `["current"]`. BoltFFI resolves `current` to the active machine target, dedupes it against explicit entries, and writes one native output per resolved host target under `dist/java/native/<host-target>/`. `boltffi pack java --no-build` is still intentionally unsupported; rerun without `--no-build`.

Current Phase 4 support is intentionally narrow:

- Current-host packaging works on `darwin-arm64`, `darwin-x86_64`, `linux-x86_64`, `linux-aarch64`, and `windows-x86_64`
- Cross-host packaging is supported for the initial parity set, including the macOS release case of `linux-x86_64`
- Cross-host targets must be fully configured up front; BoltFFI fails early instead of silently skipping them

For the macOS to Linux desktop case, install the Rust target and provide a Linux linker that Cargo can use, for example via `CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER` or `BOLTFFI_JAVA_LINKER_X86_64_UNKNOWN_LINUX_GNU`.

Cross-host JNI compilation also needs target-appropriate JNI headers. If the active `JAVA_HOME` does not contain the target platform header directory, point BoltFFI at a target-specific JDK or header directory:

- `BOLTFFI_JAVA_HOME_X86_64_UNKNOWN_LINUX_GNU=/path/to/linux-jdk`
- or `BOLTFFI_JAVA_INCLUDE_X86_64_UNKNOWN_LINUX_GNU=/path/to/linux-jdk/include/linux`

Do not pass Cargo `--target` through `--cargo-arg` for `pack java`; BoltFFI resolves JVM targets from `targets.java.jvm.host_targets`.

The `min_version` controls which language features the generated code uses:

| Version | Features                                                                 |
| ------- | ------------------------------------------------------------------------ |
| 8       | `final class` records, `abstract class` enums, `CompletableFuture` async |
| 16      | `record` types for structs                                               |
| 17      | `sealed interface` for enums with data                                   |
| 21      | Virtual thread blocking calls for async                                  |

### Output Structure

```
dist/java/
├── com/example/mylib/
│   ├── MyLib.java          # Functions module
│   ├── Native.java         # JNI native declarations
│   ├── WireReader.java     # Runtime: binary decoding
│   ├── WireWriter.java     # Runtime: binary encoding
│   └── ... (records, enums, callbacks, etc.)
├── jni/
│   ├── jni_glue.c          # JNI bridge implementation
│   └── mylib.h             # C header
├── libmylib_jni.dylib      # Flat compatibility copy for the current host (.so/.dll elsewhere)
└── native/
    └── darwin-arm64/
        └── libmylib_jni.dylib
```

### Using in a Project

1. Copy the Java sources from `dist/java/com/...` into your source tree
2. Bundle the matching `dist/java/native/<host-target>/lib*_jni.*` native library in your JAR resources. Generated desktop bindings will extract and `System.load(...)` bundled natives automatically. If you do not bundle the native library, place it where `System.loadLibrary` can find it.

```java
import com.example.mylib.*;

int result = MyLib.someExportedFunction();
```

The native library is loaded automatically when you first call into the generated bindings.

### Using with Gradle

```kotlin
dependencies {
    implementation(files("libs/mylib-sources.jar"))
}
```

Set the library path in your run configuration:

If you are not bundling native libraries into the JAR, you can still point tests at an external native directory:

```kotlin
tasks.test {
    jvmArgs("-Djava.library.path=libs/native")
}
```

<h2 id="c-sharp-packaging">C# Packaging</h2>

Package everything for .NET:

```bash
boltffi pack csharp --release
```

This generates the C# bindings, builds the Rust `cdylib` for each configured runtime identifier,
and bundles the bindings and native runtime assets into a single NuGet package.

Your Rust crate must include `cdylib` in `crate-type`:

```toml
[lib]
crate-type = ["staticlib", "cdylib"]
```

### Configuration

Enable C# in `boltffi.toml`:

```toml
[targets.csharp]
enabled = true
output = "dist/csharp"
package_id = "MyOrg.MyLib"
target_framework = "net10.0"
runtime_identifiers = ["osx-arm64", "linux-x64", "win-x64", "win-arm64"]
```

`runtime_identifiers` controls which native assets are built and bundled into the NuGet package.
`current` resolves to the active host RID; canonical values are `osx-arm64`, `osx-x64`,
`linux-x64`, `linux-arm64`, `win-x64`, and `win-arm64`. Windows hosts can build both
Windows architectures when the corresponding Rust target and Visual Studio C++ components are installed.

### Output Structure

```
dist/csharp/
├── BoltFFI.CSharp.csproj
├── src/
│   ├── MyLib.cs            # Functions, native declarations, and runtime helpers
│   ├── Point.cs            # Generated records
│   ├── Status.cs           # Generated enums
│   └── Sensor.cs           # Generated classes and stream methods
├── runtimes/
│   ├── osx-arm64/native/libmylib.dylib
│   ├── linux-x64/native/libmylib.so
│   ├── win-x64/native/mylib.dll
│   └── win-arm64/native/mylib.dll
└── packages/
    └── MyLib.0.1.0.nupkg
```

### Using the NuGet Package

Add the package output directory as a local NuGet source and reference the package from your
.NET project:

```bash
dotnet nuget add source ./dist/csharp/packages --name boltffi-local
dotnet add package MyLib
```

Then call the generated bindings from C#:

```csharp
using MyLib;

Point a = new Point(0.0, 0.0);
Point b = new Point(3.0, 4.0);
double distance = MyLib.Distance(a, b);
```

The native runtime assets are loaded automatically from the NuGet `runtimes/<rid>/native/`
folder. Classes implement `IDisposable`; streams return `IAsyncEnumerable<T>` and can be consumed
with `await foreach`.

### Source-Only Generation

If you prefer to integrate the bindings into an existing .NET project without using the NuGet
package, run:

```bash
boltffi generate csharp
```

This writes only the `.cs` files to `dist/csharp/`. You are responsible for building and shipping
the matching Rust `cdylib` alongside your application.

## WASM Packaging

Package everything for WebAssembly:

```bash
boltffi pack wasm --release
```

This compiles to wasm32-unknown-unknown, runs wasm-opt for size optimization, generates TypeScript bindings, and produces an npm package ready to publish or use locally.

### Output Structure

```
dist/wasm/pkg/
├── wasm_demo_bg.wasm      # Compiled WASM binary
├── wasm_demo.js           # Generated bindings
├── wasm_demo.d.ts         # TypeScript declarations
├── bundler.js             # Entrypoint for bundlers (Vite, webpack)
├── web.js                 # Entrypoint for browsers
├── node.js                # Entrypoint for Node.js
├── package.json           # npm package manifest
└── README.md
```

### Entrypoint Behavior

Each entrypoint handles WASM loading differently:

**Core module (`mylib.js`):**

- Exports `init(source: BufferSource | Response): Promise<void>` for manual initialization
- Exports all generated API functions
- Functions throw `Error` if called before `init()` resolves

**Loader entrypoints (`bundler.js`, `web.js`, `node.js`):**

- Export `initialized: Promise<void>` that resolves when WASM is ready
- Export all generated API functions
- Functions throw `Error` if called before `initialized` resolves

Loading strategy by entrypoint:

- `bundler.js` - relies on bundler WASM asset handling (Vite, webpack)
- `web.js` - loads WASM via `fetch()` from package-relative location
- `node.js` - loads WASM via `fs.readFile()` from disk

### Configuration

Set the npm package name in `boltffi.toml`:

```toml
[targets.wasm.npm]
package_name = "@myorg/mylib"
targets = ["bundler", "web", "nodejs"]
```

The `targets` array controls which entrypoints are generated. Include only what you need.

### wasm-opt

By default, release builds run wasm-opt for size optimization:

```toml
[targets.wasm.optimize]
enabled = true
level = "s"          # optimize for size
strip_debug = true   # remove debug info
```

Set `enabled = false` during development for faster builds.

wasm-opt refuses to touch a module that uses features it has not been told
about. It normally reads them from the `target_features` section rustc emits,
but `strip = true` in your release profile deletes that section, and then
optimization fails on the `memory.copy` rustc generates:

```
[wasm-validator error in function 2] unexpected false: memory.copy operations
require bulk memory operations [--enable-bulk-memory-opt]
```

boltffi passes the features explicitly instead, reading them from your own cargo
build so anything you enable through `RUSTFLAGS`, `.cargo/config.toml` or a
pinned toolchain is included. Stripping in release needs no extra configuration.

### Using in a Bundler (Vite, webpack)

Install the package and import directly:

```typescript
import { someExportedFunction, MyClass } from "@myorg/mylib";

const result = someExportedFunction();
const instance = MyClass.create();
```

Your bundler handles WASM loading automatically.

### Using in Node.js

Import from the package and await initialization:

```typescript
import { initialized, someExportedFunction } from "@myorg/mylib";

await initialized;
const result = someExportedFunction();
```

The `initialized` promise resolves once the WASM module is loaded and ready.

### Using in a Browser (no bundler)

Use the web entrypoint with a script tag or dynamic import:

```html
<script type="module">
  import init, { someExportedFunction } from "./pkg/web.js";
  
  await init();
  const result = someExportedFunction();
</script>
```

The `init()` function fetches the WASM file relative to the script location.

### Package Exports

The generated `package.json` includes conditional exports for all enabled environments.

With all targets enabled (`targets = ["bundler", "web", "nodejs"]`):

```json
{
  "name": "@myorg/mylib",
  "type": "module",
  "exports": {
    ".": {
      "types": "./mylib.d.ts",
      "browser": "./web.js",
      "node": "./node.js",
      "default": "./bundler.js"
    }
  }
}
```

With only Node.js (`targets = ["nodejs"]`):

```json
{
  "exports": {
    ".": {
      "types": "./mylib.d.ts",
      "node": "./node.js",
      "default": "./node.js"
    }
  }
}
```

The `default` condition resolves to `bundler.js` if enabled, else `web.js` if enabled, else `node.js`.

### Publishing to npm

Publish with:

```bash
cd dist/wasm/pkg
npm publish
```

## Python Packaging

Package the generated Python sources plus the current-host Rust shared library.

```bash
boltffi pack python
```

This flow does the work a Python package needs.

1. regenerates the Python source package unless `--regenerate false`
2. builds or reuses the host `cdylib`
3. stages the shared library into the generated package
4. builds a wheel with `python -m pip wheel`

### Configuration

Enable the target in `boltffi.toml`.

```toml
[targets.python]
enabled = true
module_name = "demo_runtime"

[targets.python.wheel]
output = "dist/python/wheelhouse"
interpreters = ["python3.11", "python3.12", "python3.13"]
```

`module_name` controls the generated import path. `output` controls where wheels land. `interpreters` defines the Python version matrix for the current host. You can override the configured interpreter list per command with repeated `--python` flags.

### Output Structure

```
dist/python/
├── demo_runtime/
│   ├── __init__.py
│   ├── __init__.pyi
│   ├── _native.c
│   ├── py.typed
│   └── libdemo_ffi.dylib
├── pyproject.toml
├── setup.py
└── wheelhouse/
    ├── demo_package-0.1.0-cp311-...whl
    └── demo_package-0.1.0-cp312-...whl
```

The shared library filename changes per host.

- macOS: `.dylib`
- Linux: `.so`
- Windows: `.dll`

### Interpreter Selection

To package for specific installed interpreters on the current host.

```bash
boltffi pack python --python python3.12 --python python3.13
```

If you omit `--python` and do not configure `[targets.python.wheel].interpreters`, BoltFFI falls back to the first available default interpreter on the host.

## Build Profiles

Debug builds are faster but produce larger, slower binaries:

```bash
boltffi pack apple           # debug
boltffi pack apple --release # optimized
```

Always use `--release` for distribution. Debug builds include symbols and skip optimizations, resulting in binaries 5-10x larger than release builds.

## Skipping Steps

If you've already built and just want to repackage:

```bash
boltffi pack apple --no-build         # skip cargo build
boltffi pack apple --xcframework-only # skip SwiftPM package
boltffi pack apple --spm-only         # skip xcframework
```

Force regeneration of bindings:

```bash
boltffi pack apple --regenerate
```

These options are useful during development when iterating on specific parts of the pipeline.

## Full Release Pipeline

The `release` command runs check, build, generate, and pack in one shot:

```bash
boltffi release apple
boltffi release android
boltffi release java
boltffi release wasm
boltffi release all --experimental
```

Or build everything at once:

```bash
boltffi release all
```

---

Source: https://boltffi.dev/docs/configuration

# Configuration

BoltFFI reads its configuration from a file called `boltffi.toml` in your project's root directory. This file tells BoltFFI how to name your modules, where to put generated files, and how to structure the output packages. Run `boltffi init` to create one with sensible defaults, then customize it for your needs.

## Overlay Configs

BoltFFI always starts from `./boltffi.toml`. For one-off builds, you can merge an additional TOML
file on top with the global `--overlay` flag:

```bash
boltffi --overlay boltffi.ci.toml pack android
boltffi --overlay boltffi.release.toml pack all --release
```

This is useful when you want CI or release-specific settings without changing the tracked base
config. The base `boltffi.toml` must still exist; the overlay only augments or overrides values in
that file for the current command. `boltffi init` is the one command that intentionally rejects
`--overlay`.

## Package Identity

Every `boltffi.toml` starts with a `[package]` section that identifies your library:

```toml
[package]
name = "mylib"
```

The `name` field is used to derive default names throughout the pipeline. For example, if your package name is `mylib`, BoltFFI generates a Swift module called `MyLib` and a Kotlin class called `MyLib`. All output paths also use this name as a base.

If your Rust crate has a different name than what you want to expose (perhaps your crate is `my_lib` with underscores but you want `mylib`), specify both:

```toml
[package]
name = "mylib"
crate = "my_lib"
```

BoltFFI scans and builds the crate specified by `crate`, but uses `name` for all generated module names and paths.

## Apple Configuration

The `[targets.apple]` section controls iOS, iOS Simulator, and optionally macOS builds.

### Output Directory

All Apple artifacts go into a single root directory. By default this is `dist/apple`, but you can change it:

```toml
[targets.apple]
output = "build/ios"
```

After running `boltffi pack apple`, this directory contains your xcframework, Package.swift, and generated Swift sources.

### Deployment Target

The deployment target sets the minimum iOS version your library supports. This affects which APIs are available and which devices can run your code:

```toml
[targets.apple]
deployment_target = "15.0"
```

The default is `16.0`. Lower this if you need to support older devices, but be aware that some Swift concurrency features require iOS 13+ and full async/await requires iOS 15+.

### Including macOS

By default, BoltFFI only builds for iOS and iOS Simulator. If your library also needs to run on Mac, enable macOS builds:

```toml
[targets.apple]
include_macos = true
```

This adds macOS slices to your xcframework, making it usable in Mac Catalyst apps and native macOS applications.

### Apple Slice Selection

BoltFFI resolves Apple slices from `boltffi.toml` instead of always packaging the full default matrix. By default it builds:

- iOS device: `arm64`
- iOS Simulator: `arm64`, `x86_64`
- macOS: disabled unless `include_macos = true`

You can narrow that matrix explicitly:

```toml
[targets.apple]
ios_architectures = ["arm64"]
simulator_architectures = ["arm64"]
include_macos = true
macos_architectures = ["arm64"]
```

Each Apple architecture list can be set to `[]` to exclude that slice family. `macos_architectures`
only applies when `include_macos = true`. When a list is omitted, BoltFFI keeps the current
defaults. BoltFFI still requires at least one Apple slice across device, simulator, or enabled
macOS targets.

### Swift Module Name

The generated Swift code is organized into a module. By default, BoltFFI converts your package name to PascalCase (`mylib` becomes `MyLib`). Override this if you want a different name:

```toml
[targets.apple.swift]
module_name = "MyLibrary"
```

This name appears in your Swift import statements: `import MyLibrary`.

### SwiftPM Layouts

The layout determines how BoltFFI structures the SwiftPM package. This is the most important configuration choice for Apple because it affects how you integrate the library into your Xcode project.

### Debug Symbols

If you want a companion archive of the unstripped Apple slice libraries, enable `debug_symbols`:

```toml
[targets.apple.debug_symbols]
enabled = true
output = "dist/apple/symbols"
```

`boltffi pack apple` writes `{XcframeworkName}.xcframework.symbols.zip` to that directory. The archive includes a `symbols.json` manifest plus one unstripped static library per discovered Apple target slice.

For release-like packaging profiles, BoltFFI now requires Cargo debuginfo to be enabled before it will emit this archive. In practice that usually means:

```toml
[profile.release]
debug = true
```

#### ffi-only Layout

This is the default and simplest option. BoltFFI creates a self-contained SwiftPM package with everything inside:

```toml
[targets.apple.spm]
layout = "ffi-only"
```

**When to use:** You want a drop-in package that works immediately. No additional setup required.

**Output structure:**

```
dist/apple/
├── MyLib.xcframework/
│   ├── ios-arm64/
│   │   ├── Headers/mylib.h
│   │   └── libmylib.a
│   └── ios-arm64_x86_64-simulator/
│       ├── Headers/mylib.h
│       └── libmylib.a
├── Package.swift
└── Sources/
    └── BoltFFI/
        └── MyLibBoltFFI.swift
```

**Generated Package.swift:**

```swift
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyLib",
    platforms: [.iOS(.v16)],
    products: [
        .library(name: "MyLib", targets: ["MyLib"])
    ],
    targets: [
        .binaryTarget(
            name: "MyLibFFI",
            path: "MyLib.xcframework"
        ),
        .target(
            name: "MyLib",
            dependencies: ["MyLibFFI"],
            path: "Sources/BoltFFI"
        )
    ]
)
```

To use this package in Xcode, add it as a local package dependency pointing to `dist/apple`, then `import MyLib` in your Swift code.

#### bundled Layout

Use this when you have an existing Swift package and want to add BoltFFI-generated bindings to it. BoltFFI places the generated Swift inside your existing source directory:

```toml
[targets.apple.spm]
layout = "bundled"
wrapper_sources = "Sources/MyWrapper"
```

**When to use:** You're adding Rust functionality to an existing Swift package, or you want to write additional Swift wrapper code alongside the generated bindings.

**Output structure:**

```
dist/apple/
├── MyLib.xcframework/
├── Package.swift
└── Sources/
    └── MyWrapper/
        ├── YourExistingCode.swift
        └── BoltFFI/
            └── MyLibBoltFFI.swift
```

The `wrapper_sources` path tells BoltFFI where your existing Swift target lives. Generated bindings go into a `Riff` subdirectory inside that path. Your Package.swift should already have a target pointing at `Sources/MyWrapper`.

#### split Layout

Use this when you want maximum control. BoltFFI creates a binary-only package containing just the xcframework, and writes the generated Swift to a separate location:

```toml
[targets.apple.spm]
layout = "split"

[targets.apple.swift]
output = "Sources/Generated"
```

**When to use:** You maintain your own SwiftPM package structure and just need the xcframework and generated code as raw ingredients.

**Output structure:**

```
dist/apple/
├── MyLib.xcframework/
└── Package.swift          # binary target only

Sources/
└── Generated/
    └── BoltFFI/
        └── MyLibBoltFFI.swift
```

**Generated Package.swift:**

```swift
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyLibFFI",
    platforms: [.iOS(.v16)],
    products: [
        .library(name: "MyLibFFI", targets: ["MyLibFFI"])
    ],
    targets: [
        .binaryTarget(
            name: "MyLibFFI",
            path: "MyLib.xcframework"
        )
    ]
)
```

You then create your own package that depends on `MyLibFFI` and includes the generated Swift from `Sources/Generated`.

### Remote Distribution

By default, Package.swift references the xcframework via a local file path. This works for development and when you bundle the package directly in your app repository. For distributing your library to others, switch to remote distribution:

```toml
[targets.apple.spm]
distribution = "remote"
repo_url = "https://github.com/yourname/mylib/releases/download"
```

Then package with a version:

```bash
boltffi pack apple --release --version 1.0.0
```

**Generated Package.swift:**

```swift
.binaryTarget(
    name: "MyLibFFI",
    url: "https://github.com/yourname/mylib/releases/download/1.0.0/MyLib.xcframework.zip",
    checksum: "abc123..."
)
```

Upload `MyLib.xcframework.zip` to your GitHub release. Consumers can then add your package by URL without needing the source.

### Type Mappings

If you use custom types that should map to native Swift types, configure type mappings. For example, if your Rust code uses a `Uuid` type that should become Swift's `UUID`:

```toml
[targets.apple.swift.type_mappings]
Uuid = { type = "UUID", conversion = "uuid_string" }
```

The generated Swift uses `UUID` directly instead of a wrapper type. BoltFFI handles the string conversion automatically at the FFI boundary.

Available conversions:

- `uuid_string`: Converts between String and UUID
- `url_string`: Converts between String and URL

## Android Configuration

The `[targets.android]` section controls builds for all Android ABIs.

### Output Directory

All Android artifacts go into a single root directory:

```toml
[targets.android]
output = "build/android"
```

The default is `dist/android`. After running `boltffi pack android`, this directory contains your jniLibs and Kotlin sources.

### Minimum SDK

Set the minimum Android API level:

```toml
[targets.android]
min_sdk = 21
```

The default is `24` (Android 7.0). Lower values support more devices but may limit available APIs.

### Kotlin Package

Generated Kotlin code needs a package name. By default, BoltFFI uses `com.example.{name}`:

```toml
[targets.android.kotlin]
package = "com.mycompany.mylib"
```

This determines the directory structure of the generated files and the package declaration in the Kotlin source.

### API Style

Choose how exported functions appear in Kotlin:

```toml
[targets.android.kotlin]
api_style = "top_level"
```

**top\_level** (default): Functions are top-level Kotlin functions. Import the package and call directly:

```kotlin
import com.mycompany.mylib.*

val result = processData(input)
```

**module\_object**: Functions are methods on an object. Useful if you want to namespace everything:

```toml
[targets.android.kotlin]
api_style = "module_object"
module_name = "MyLib"
```

```kotlin
import com.mycompany.mylib.MyLib

val result = MyLib.processData(input)
```

### Factory Style

When your Rust code has factory functions (functions that create class instances), choose how they appear in Kotlin:

```toml
[targets.android.kotlin]
factory_style = "constructors"
```

**constructors** (default): Factory functions become Kotlin constructors:

```kotlin
val client = HttpClient(baseUrl)
```

**companion\_methods**: Factory functions become companion object methods:

```kotlin
val client = HttpClient.create(baseUrl)
```

### Output Structure

After running `boltffi pack android`, you get:

```
dist/android/
├── jniLibs/
│   ├── arm64-v8a/
│   │   └── libmylib.so
│   ├── armeabi-v7a/
│   │   └── libmylib.so
│   ├── x86/
│   │   └── libmylib.so
│   └── x86_64/
│       └── libmylib.so
└── kotlin/
    ├── com/mycompany/mylib/
    │   └── MyLib.kt
    └── jni/
        └── jni_glue.c
```

Copy `jniLibs` to your Android project's `src/main/` directory. Copy the Kotlin sources to your source set. The native libraries are loaded automatically when you first use the Kotlin bindings.

### Android Debug Symbols

To keep a companion archive of the unstripped JNI libraries per ABI:

```toml
[targets.android.debug_symbols]
enabled = true
output = "dist/android/symbols"
```

`boltffi pack android` writes `{crate_artifact_name}.android.symbols.zip` to that directory. The archive mirrors the `jniLibs/<abi>/` layout and includes a `symbols.json` manifest with ABI and target mappings.

For release-like packaging profiles, BoltFFI requires Cargo debuginfo to be enabled before it will emit this archive.

## Kotlin Multiplatform Configuration

The `[targets.kotlin_multiplatform]` section is experimental and controls `boltffi generate kmp --experimental`.

```toml
experimental = ["kotlin_multiplatform"]

[targets.kotlin_multiplatform]
enabled = true
output = "dist/kotlin-multiplatform"
package = "com.mycompany.mylib"
module_name = "MyLib"
# Experimental diagnostic mode. Default is false.
preview_prune_unsupported = false
```

Generated output is a Kotlin Multiplatform Gradle module with `commonMain` declarations and `jvmMain`/`androidMain` actuals backed by the existing Kotlin/JNI generator. Kotlin/Native `cinterop` actuals for iOS/macOS are not generated yet.

KMP generation is strict by default. Unsupported exported APIs fail generation so `commonMain` never silently exposes a partial contract. Setting `preview_prune_unsupported = true` omits unsupported APIs, writes `boltffi-kmp-support.json`, and marks the generated module as pruned for diagnostic iteration.

If `package` or `module_name` is omitted, BoltFFI reuses the corresponding Android Kotlin defaults.

`boltffi pack kmp` writes JVM desktop native resources under `src/jvmMain/resources/native/<host-target>/`. Those JVM host targets come from `[targets.java.jvm].host_targets`, which defaults to `["current"]`; `targets.java.jvm.enabled` does not need to be true for KMP packaging to use the shared host matrix.

## Java Configuration

The `[targets.java]` section controls Java bindings for both JVM and Android.

### Package Name

Generated Java code needs a package name. By default, BoltFFI uses `com.example.{name}`:

```toml
[targets.java]
package = "com.mycompany.mylib"
```

This determines the directory structure and the `package` declaration in every generated `.java` file.

### Module Name

The generated functions class uses a name derived from your package name. Override it if needed:

```toml
[targets.java]
module_name = "MyLib"
```

This becomes the class name for the static functions module: `MyLib.someFunction()`.

### Minimum Java Version

The `min_version` controls which Java language features the generated code uses:

```toml
[targets.java]
min_version = 8
```

| Version | Generated code uses                                                                            |
| ------- | ---------------------------------------------------------------------------------------------- |
| 8       | `final class` for records, `abstract class` for enums with data, `CompletableFuture` for async |
| 16      | `record` types for structs                                                                     |
| 17      | `sealed interface` + `record` for enums with data                                              |
| 21      | Virtual thread blocking calls for async (no `CompletableFuture`)                               |

### JVM Target

Enable JVM packaging to produce a standalone JNI library for desktop or server use:

```toml
[targets.java.jvm]
enabled = true
output = "dist/java"
host_targets = ["current", "linux-x86_64"]
```

The default output is `dist/java`. This produces Java sources, a C header, JNI glue, a structured native output under `dist/java/native/<host-target>/`, and a flat current-host `_jni` compatibility copy in `dist/java/`. `host_targets` defaults to `["current"]`, supports aliases such as `darwin-aarch64` and `linux-x86-64`, resolves `current` to the active host, and dedupes repeated entries. Requires `JAVA_HOME` and `clang`.

Phase 4 extends JVM packaging from current-host-only output to explicit desktop host matrices. Current-host packaging works on `darwin-arm64`, `darwin-x86_64`, `linux-x86_64`, `linux-aarch64`, and `windows-x86_64`. The initial cross-host parity path is macOS packaging `linux-x86_64`, which requires the Rust target, a configured Linux linker such as `CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER` or `BOLTFFI_JAVA_LINKER_X86_64_UNKNOWN_LINUX_GNU`, and target-appropriate JNI headers via either `BOLTFFI_JAVA_HOME_X86_64_UNKNOWN_LINUX_GNU` or `BOLTFFI_JAVA_INCLUDE_X86_64_UNKNOWN_LINUX_GNU`. `pack java` rejects explicit Cargo `--target` passthrough args because the host matrix is controlled by `host_targets`.

### JVM Debug Symbols

To archive the unstripped desktop JNI libraries per packaged host target:

```toml
[targets.java.jvm.debug_symbols]
enabled = true
output = "dist/java/symbols"
```

`boltffi pack java` writes `{selected_artifact_name}.jvm.symbols.zip` to that directory. The archive mirrors `native/<host-target>/` and includes a `symbols.json` manifest with host-target mappings.

For release-like packaging profiles, BoltFFI requires Cargo debuginfo to be enabled before it will emit this archive. On Windows MSVC hosts, the archive also includes sibling `.pdb` files for the JNI DLL and any bundled Rust DLL when those sidecars exist.

### Android Target

Enable Android packaging to produce shared libraries for all Android ABIs:

```toml
[targets.java.android]
enabled = true
output = "dist/java/android"
min_sdk = 24
```

The default output is `dist/java/android`. The `min_sdk` sets the minimum Android API level (default `24`).

Both JVM and Android targets can be enabled simultaneously. They share the same `package`, `module_name`, and `min_version` settings but produce separate output.

<h2 id="c-sharp-configuration">C# Configuration</h2>

The `[targets.csharp]` section controls C# generation and NuGet packaging.

### Output Directory

C# artifacts go into `dist/csharp` by default:

```toml
[targets.csharp]
enabled = true
output = "dist/csharp"
```

Run generation or packaging with:

```bash
boltffi generate csharp   # writes .cs files only
boltffi pack csharp       # builds the NuGet package with native runtime assets
```

`generate csharp` writes one `.cs` file for the top-level module/runtime helpers plus additional `.cs` files for generated records, enums, classes, callbacks, and stream-owning types directly under `output`. `pack csharp` writes the generated sources under `{output}/src`, builds the Rust `cdylib` for each configured runtime identifier into `{output}/runtimes/<rid>/native/`, and emits a `.nupkg` under `{output}/packages/` that bundles everything together.

### Namespace

By default, C# sources use a namespace derived from the Rust crate name. Set `namespace` when your generated C# types need to live under an application or product namespace:

```toml
[targets.csharp]
enabled = true
namespace = "CounterApp.Shared"
```

### Runtime Identifiers

Choose which .NET runtime identifiers `pack csharp` builds native assets for:

```toml
[targets.csharp]
enabled = true
runtime_identifiers = ["osx-arm64", "linux-x64", "win-x64", "win-arm64"]
```

`current` resolves to the active host RID. Supported canonical values are `osx-arm64`, `osx-x64`, `linux-x64`, `linux-arm64`, `win-x64`, and `win-arm64`. A Windows host can build both Windows architectures when the corresponding Rust target and Visual Studio C++ components are installed. See [BOLTFFI\_TOML\_SPEC.md](https://github.com/boltffi/boltffi/blob/main/BOLTFFI_TOML_SPEC.md) for the full list of options including `package_id`, `target_framework`, and `package_output`.

## WASM Configuration

The `[targets.wasm]` section controls WebAssembly builds and npm package generation.

### Output Directory

All WASM artifacts go into a single root directory:

```toml
[targets.wasm]
output = "dist/wasm"
```

The default is `dist/wasm`. After running `boltffi pack wasm`, this directory contains your compiled WASM binary, TypeScript bindings, and npm package files.

### Build Profile

Control whether to build debug or release:

```toml
[targets.wasm]
profile = "release"
```

The default is `release`. Use `debug` during development for faster builds and better error messages, but always ship `release` builds.

### wasm-opt Optimization

Release builds run `wasm-opt` to reduce binary size:

```toml
[targets.wasm.optimize]
enabled = true
level = "s"          # optimize for size
strip_debug = true   # remove debug info
```

Available optimization levels:

- `0` through `4`: increasing optimization (4 is most aggressive)
- `s`: optimize for size (default, recommended)
- `z`: optimize aggressively for size

Set `enabled = false` during development to skip optimization and speed up builds.

### TypeScript Module Name

The generated TypeScript uses a module name derived from your package name. Override it if needed:

```toml
[targets.wasm.typescript]
module_name = "mylib"
output = "dist/wasm/pkg"
```

This affects the generated filenames: `mylib.js`, `mylib.d.ts`, `mylib_bg.wasm`.

### npm Package

Configure the npm package that `boltffi pack wasm` generates:

```toml
[targets.wasm.npm]
package_name = "@mycompany/mylib"
targets = ["bundler", "web", "nodejs"]
```

The `package_name` field is required for packaging. Include the scope if publishing to a scoped npm registry.

The `targets` array controls which environment entrypoints are generated:

- `bundler`: For Vite, webpack, and other bundlers that handle WASM loading
- `web`: For browsers without a bundler (uses `fetch()` to load WASM)
- `nodejs`: For Node.js (uses `fs.readFile()` to load WASM)

Include only the targets you need. Each generates a separate entrypoint file.

### npm Package Metadata

Add metadata to the generated `package.json`:

```toml
[targets.wasm.npm]
package_name = "@mycompany/mylib"
version = "1.0.0"
license = "MIT"
repository = "https://github.com/mycompany/mylib"
generate_package_json = true
generate_readme = true
```

If `version`, `license`, or `repository` are not set, they fall back to the values in `[package]` or your `Cargo.toml`.

### Type Mappings

Map custom types to native TypeScript types:

```toml
[targets.wasm.typescript.type_mappings]
Uuid = { type = "string", conversion = "uuid_string" }
```

Since TypeScript doesn't have native UUID or URL types, these typically map to `string`.

## Python Configuration

Python has two package boundaries.

- generation writes the Python source package
- packaging builds the host Rust shared library and produces wheels

The generated package root defaults to `dist/python`.

```toml
[targets.python]
enabled = true
```

### Module Name

By default, BoltFFI uses the Rust crate artifact name for the Python package module. Override it if you want a different import name.

```toml
[targets.python]
enabled = true
module_name = "demo_runtime"
```

This changes the generated package directory and the import path.

```python
import demo_runtime
```

### Wheel Output

Python packaging writes wheels into `dist/python/wheelhouse` by default. Override that under `[targets.python.wheel]`.

```toml
[targets.python]
enabled = true

[targets.python.wheel]
output = "dist/python/wheels"
```

### Python Interpreter Matrix

`boltffi pack python` targets the current host platform, but it can build wheels for more than one installed Python interpreter on that host. Configure the interpreter commands explicitly when you want a stable packaging matrix.

```toml
[targets.python]
enabled = true

[targets.python.wheel]
interpreters = ["python3.11", "python3.12", "python3.13"]
```

Each value is an interpreter executable or path that BoltFFI resolves before packaging. You can also override this per command with repeated `--python` flags.

```bash
boltffi pack python --python python3.12 --python python3.13
```

If you omit `interpreters`, BoltFFI falls back to the first available default interpreter it finds on the host.

## Complete Example

Here's a full configuration for a library that targets all platforms:

```toml
[package]
name = "mylib"

[targets.apple]
output = "dist/apple"
deployment_target = "15.0"
include_macos = true
ios_architectures = ["arm64"]
simulator_architectures = ["arm64", "x86_64"]
macos_architectures = ["arm64", "x86_64"]

[targets.apple.swift]
module_name = "MyLib"

[targets.apple.swift.type_mappings]
Uuid = { type = "UUID", conversion = "uuid_string" }

[targets.apple.spm]
layout = "ffi-only"
distribution = "local"

[targets.android]
output = "dist/android"
min_sdk = 24

[targets.android.kotlin]
package = "com.mycompany.mylib"
api_style = "top_level"
factory_style = "constructors"

[targets.java]
package = "com.mycompany.mylib"
min_version = 8

[targets.java.jvm]
enabled = true

[targets.csharp]
enabled = true
output = "dist/csharp"

[targets.wasm]
output = "dist/wasm"

[targets.wasm.npm]
package_name = "@mycompany/mylib"
targets = ["bundler", "web", "nodejs"]

[targets.wasm.optimize]
enabled = true
level = "s"

[targets.python]
enabled = true
module_name = "mylib_runtime"

[targets.python.wheel]
output = "dist/python/wheels"
interpreters = ["python3.11", "python3.12"]
```

---

Source: https://boltffi.dev/docs/experimental

# Experimental Features

Some BoltFFI features are behind an experimental flag. The API for these features may change between releases, so they require an explicit opt-in.

Java, C#, and Python bindings are generally available and no longer require experimental opt-in.

| Feature                     | Key                        | Status                      |
| --------------------------- | -------------------------- | --------------------------- |
| Dart target                 | `dart`                     | In progress                 |
| Kotlin Multiplatform target | `kotlin_multiplatform`     | Initial JVM/Android actuals |
| TS async streams            | `typescript.async_streams` | Functional                  |

## Enabling

### CLI flag

Pass `--experimental` to include experimental targets during that command run:

```bash
boltffi generate all --experimental
boltffi pack all --experimental
```

### Config file

Add the feature key to `experimental` in `boltffi.toml`:

```toml
experimental = ["kotlin_multiplatform", "typescript.async_streams"]
```

The CLI flag applies to a single command. The config array applies to every build.

## Feature Details

### Kotlin Multiplatform

**Key:** `kotlin_multiplatform`

Enables `boltffi generate kmp` and includes Kotlin Multiplatform output during `boltffi generate all --experimental`. The generated module contains `commonMain` declarations plus `jvmMain` and `androidMain` actuals that delegate to the existing Kotlin/JNI bindings.

This is an initial target slice. Kotlin/Native `cinterop` actuals for iOS/macOS are not generated yet.

### TypeScript Async Streams

**Key:** `typescript.async_streams`

Enables `AsyncIterable` stream generation for TypeScript/WASM targets. Without this flag, stream methods are skipped during TypeScript codegen.

## Graduating from Experimental

When a feature stabilizes, it moves out of the experimental list. The `--experimental` flag and config entry are no longer required. Existing code continues to work without changes.

---

Source: https://boltffi.dev/docs/async-internals

# Async Internals

This page explains how BoltFFI bridges Rust futures to target language async systems. You don't need to understand this to use async functions, but it helps when debugging or optimizing async code.

## The Polling Model

BoltFFI wraps each Rust future in a `RustFuture<T>` that exposes a C-compatible interface. The bindings call `poll` with a continuation callback. If the future is pending, Rust stores that callback and returns; when the future wakes, Rust invokes the callback with `MaybeReady`, and bindings poll again. When the callback reports `Ready`, bindings exit the cycle, then run `complete` and `free`.

### Who does what, and when

1. **Bindings pull**: call `entry` to create a handle.
2. **Bindings pull**: call `poll(handle, continuation)` once.
3. **Rust polls once**:
   - if ready, Rust immediately invokes continuation with `Ready`;
   - if pending, Rust stores continuation and returns.
4. **Rust pushes wake signal**: when the underlying future wakes, Rust invokes continuation with `MaybeReady`.
5. **Bindings pull again**: on that callback, bindings call `poll` again.
6. Repeat steps 3-5 until callback is `Ready`.
7. **Bindings finalize**: call `complete`, then `free`.

There is no busy polling loop in user code. Between polls, bindings wait for Rust to invoke the continuation.

<svg viewBox="0 0 940 700" class="w-full my-8" style="max-width: 940px;">
  <defs>
    <marker id="arrow" markerWidth="8" markerHeight="8" refX="8" refY="4" orient="auto">
      <path d="M0,0 L8,4 L0,8 Z" fill="#6b7280" />
    </marker>

    <marker id="arrow-dashed" markerWidth="8" markerHeight="8" refX="8" refY="4" orient="auto">
      <path d="M0,0 L8,4 L0,8 Z" fill="#6b7280" />
    </marker>
  </defs>

  <rect x="35" y="20" width="130" height="36" rx="4" fill="#1e1e2e" stroke="#89b4fa" stroke-width="1.5" />

  <text x="100" y="43" text-anchor="middle" fill="#cdd6f4" font-size="13" font-family="system-ui">User Code</text>

  <rect x="340" y="20" width="180" height="36" rx="4" fill="#1e1e2e" stroke="#a6e3a1" stroke-width="1.5" />

  <text x="430" y="43" text-anchor="middle" fill="#cdd6f4" font-size="13" font-family="system-ui">Generated Bindings</text>

  <rect x="705" y="20" width="170" height="36" rx="4" fill="#1e1e2e" stroke="#fab387" stroke-width="1.5" />

  <text x="790" y="43" text-anchor="middle" fill="#cdd6f4" font-size="13" font-family="system-ui">Rust Scaffolding</text>

  <line x1="100" y1="56" x2="100" y2="660" stroke="#89b4fa" stroke-width="1" stroke-dasharray="4,4" />

  <line x1="430" y1="56" x2="430" y2="660" stroke="#a6e3a1" stroke-width="1" stroke-dasharray="4,4" />

  <line x1="790" y1="56" x2="790" y2="660" stroke="#fab387" stroke-width="1" stroke-dasharray="4,4" />

  <line x1="100" y1="110" x2="422" y2="110" stroke="#6b7280" stroke-width="1.5" marker-end="url(#arrow)" />

  <text x="261" y="98" text-anchor="middle" fill="#cdd6f4" font-size="11" font-family="system-ui">call async function</text>

  <line x1="430" y1="165" x2="782" y2="165" stroke="#6b7280" stroke-width="1.5" marker-end="url(#arrow)" />

  <text x="606" y="153" text-anchor="middle" fill="#cdd6f4" font-size="11" font-family="system-ui">call scaffolding function</text>

  <line x1="790" y1="220" x2="438" y2="220" stroke="#6b7280" stroke-width="1.5" stroke-dasharray="6,3" marker-end="url(#arrow-dashed)" />

  <text x="606" y="208" text-anchor="middle" fill="#cdd6f4" font-size="11" font-family="system-ui">return RustFuture handle</text>

  <rect x="300" y="280" width="540" height="180" rx="6" fill="transparent" stroke="#89b4fa" stroke-width="1.5" stroke-dasharray="4,4" />

  <text x="320" y="302" fill="#89b4fa" font-size="11" font-family="system-ui">callback-driven wait/re-poll cycle</text>

  <line x1="430" y1="325" x2="782" y2="325" stroke="#6b7280" stroke-width="1.5" marker-end="url(#arrow)" />

  <text x="606" y="313" text-anchor="middle" fill="#cdd6f4" font-size="11" font-family="system-ui">bindings call RustFuture poll fn</text>

  <line x1="790" y1="395" x2="438" y2="395" stroke="#6b7280" stroke-width="1.5" marker-end="url(#arrow)" />

  <text x="606" y="383" text-anchor="middle" fill="#cdd6f4" font-size="11" font-family="system-ui">Rust invokes continuation callback</text>

  <text x="606" y="431" text-anchor="middle" fill="#6b7280" font-size="10" font-family="system-ui">MaybeReady → bindings poll again</text>
  <text x="606" y="448" text-anchor="middle" fill="#6b7280" font-size="10" font-family="system-ui">Ready → exit cycle</text>

  <line x1="430" y1="510" x2="782" y2="510" stroke="#6b7280" stroke-width="1.5" marker-end="url(#arrow)" />

  <text x="606" y="498" text-anchor="middle" fill="#cdd6f4" font-size="11" font-family="system-ui">call RustFuture complete fn</text>

  <line x1="790" y1="560" x2="438" y2="560" stroke="#6b7280" stroke-width="1.5" stroke-dasharray="6,3" marker-end="url(#arrow-dashed)" />

  <text x="606" y="548" text-anchor="middle" fill="#cdd6f4" font-size="11" font-family="system-ui">return result</text>

  <line x1="430" y1="605" x2="782" y2="605" stroke="#6b7280" stroke-width="1.5" marker-end="url(#arrow)" />

  <text x="606" y="593" text-anchor="middle" fill="#cdd6f4" font-size="11" font-family="system-ui">call RustFuture free fn</text>

  <line x1="430" y1="650" x2="108" y2="650" stroke="#6b7280" stroke-width="1.5" stroke-dasharray="6,3" marker-end="url(#arrow-dashed)" />

  <text x="261" y="638" text-anchor="middle" fill="#cdd6f4" font-size="11" font-family="system-ui">return from async function</text>
</svg>

## Generated FFI Functions

For each async function, BoltFFI generates five FFI functions:

- **entry** - Creates the `RustFuture` and returns a handle
- **poll** - Polls the future with a continuation callback
- **complete** - Extracts the result once the future is ready
- **cancel** - Marks the future as cancelled
- **free** - Deallocates the future

The bindings use a callback handshake: entry creates the handle, poll registers/waits, callback returns `MaybeReady` or `Ready`, `MaybeReady` triggers another poll, and `Ready` ends polling before `complete`/`free`.

## Continuation Callbacks

When bindings call `poll`, they pass a continuation callback plus callback data. If the future is pending, BoltFFI stores that continuation. When the future wakes (I/O completes, timer fires, etc.), BoltFFI invokes the stored callback with `MaybeReady`; bindings then poll again. A `Ready` callback means polling is finished and the call should finalize.

## Lock-Free Implementation

Continuation scheduling uses atomic state tags and compare-and-swap transitions. Future execution state and result storage are still guarded by a mutex.

## Cancellation

When the target language cancels an async operation, bindings call `cancel`. BoltFFI marks the future as cancelled and wakes any stored continuation with a `Ready` signal so waiting bindings can stop polling promptly. Cleanup then runs through `free`, and cancellation is surfaced by the target runtime's wrapper.
