ts-function
A proc-macro that generates TypeScript type aliases and wasm-bindgen ABI trait
implementations for Rust function wrapper structs, enabling strongly-typed
Typescript Functions types in ts-macro projects with little to no boilerplate.
Motivation
When using ts-macro to generate
TypeScript interfaces for Rust structs, there is no built-in support for
function types. A struct field like on_click: js_sys::Function
produces an opaque Function type in TypeScript and forces you to manually
handle raw JsValue conversions in Rust.
ts-function bridges this gap. It allows you to define function signatures in
pure Rust, automatically generating the correct TypeScript types ((args: ...) => void) and implementing the wasm-bindgen ABI traits required to pass those
functions across the Wasm boundary safely.
In Javascript land, it's common for libaries to take an object of functions to
customize behavior, and this libary makes it easy to write code like that.
Example: Unified #[ts] Attribute
The #[ts] attribute macro can generate TypeScript bindings for structs, enums, and functions.
For enums it uses wasm_bindgen's enum handling, for structs it behaves simular to ts-macro (stil hoping to upstream oneday),
and for functions which is the namesake and most complex case, they are generated from type aliases or impl blocks.
So on the Rust side, Typescript functions are represented by a struct like:
struct TypedFunction(Function) which implements a call pseudo-trait.
Call provides strong typing and type conversion, as well as easy result chaining.
The newtype struct allows us to hold the function handle.
use *;
use ts;
// 1. Define your enums - automatically applies #[wasm_bindgen]
// 2. Define your function signatures using type aliases
pub type SingleArgFn = fn;
pub type MultiArgFn = fn;
// 3. Use `#[ts]` to create a struct containing your function wrappers
// 4. Export a function that accepts your struct from JS
Generated TypeScript (index.d.ts)
wasm-bindgen outputs the types you would expect:
export enum UserStatus {
Active = 0,
Inactive = 1,
}
type SingleArgFn = (msg: string) => void;
type MultiArgFn = (a: number, b: Uint8Array) => void;
interface IAppCallbacks {
onReady: SingleArgFn;
onData: MultiArgFn;
status: UserStatus;
}
export function execute_callbacks(cbs: IAppCallbacks): void;
Example: Returning a Value
pub type CalculateFn = fn ;
// Usage:
let result: f64 = calculate_fn.call.unwrap;
Supported Types
ts-function supports a wide range of Rust types, automatically mapping them to their idiomatic TypeScript equivalents.
| Rust Type | TypeScript Type | Notes |
|---|---|---|
f32, f64, i8-i32, u8-u32 |
number |
|
i64, u64 |
bigint |
Mapped via js_sys::BigInt |
bool |
boolean |
|
String, &str |
string |
|
Option<T> |
`T | undefined` |
Vec<u8>, &[u8] |
Uint8Array |
See Zero-Copy Performance Tip |
Vec<f64>, &[f64] |
Float64Array |
Also supports i8, u16, i32, etc. |
JsValue |
any |
|
js_sys::Object |
object |
|
| Rust Struct | Typescript Object | Any JsCast type (including ts interfaces) |
| Enum | Enum | Limited to C-like enums, as ts-function uses wasm-binden for enums |
Return Values & Performance
The #[ts] macro supports returning values from JavaScript back into
Rust. It handles standard primitives, strings, options, and even numeric vectors
(via TypedArray).
Rust Structs to Javascript Objects (Round-Trip)
Structs annotated with #[ts] automatically implement Into<JsValue>. This allows you to effortlessly construct Rust structs and pass them back to Javascript or return them from function calls natively:
// In your wasm boundary:
let info = BasicInfo ;
// Converts 1-to-1 to a Javascript Object: `{ name: "Alice", age: 30.0 }`
let js_val: JsValue = info.into;
Zero-Copy Performance
When returning large arrays, using Vec<u8> or similar will force a memory copy
from the JavaScript heap to the Rust heap. To achieve zero-copy performance,
you can return a js_sys type directly:
pub type LargeDataFn = fn ;
// Usage (Zero-Copy):
let arr: Uint8Array = large_data_fn.call.unwrap;
Advanced Usage / The Escape Hatch
If you need completely custom serialization or want to embed specific side-effects and error handling directly into the function execution, you can use the escape hatch.
By applying #[ts] to an impl block for a tuple-struct wrapping js_sys::Function, you can customize the call method's implementation.
ts-function will still parse your call method's signature and automatically generate the correct TypeScript interface for it!
use *;
use ts;
// 1. Define your own tuple struct
;
// 2. Apply the macro to the impl block
License
Dual-licensed under either the MIT license or the Apache License, Version 2.0 at your option.