# ts-function
[](https://crates.io/crates/ts-function)
[](https://docs.rs/ts-function)
[](https://rust-lang.org)
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`](https://crates.io/crates/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.
```rust
use wasm_bindgen::prelude::*;
use ts_function::ts;
// 1. Define your enums - automatically applies #[wasm_bindgen]
#[ts]
pub enum UserStatus { Active, Inactive }
// 2. Define your function signatures using type aliases
#[ts]
pub type SingleArgFn = fn(msg: String);
#[ts]
pub type MultiArgFn = fn(a: f64, b: js_sys::Uint8Array);
// 3. Use `#[ts]` to create a struct containing your function wrappers
#[ts]
struct AppInit {
on_ready: SingleArgFn,
on_data: MultiArgFn,
status: UserStatus,
}
// 4. Export a function that accepts your struct from JS
#[wasm_bindgen]
pub fn execute_callbacks(callbacks: AppInit) {
// Call the functions! Arguments are automatically converted to `JsValue`.
// The `call` method returns a `Result<(), JsValue>` forwarding any JS exceptions.
callbacks.on_ready.call("System is ready".to_string()).unwrap();
let arr = js_sys::Uint8Array::new_with_length(3);
arr.copy_from(&[1, 2, 3]);
callbacks.on_data.call(42.5, arr).unwrap();
}
```
### Generated TypeScript (`index.d.ts`)
`wasm-bindgen` outputs the types you would expect:
```typescript
export enum UserStatus {
Active = 0,
Inactive = 1,
}
type SingleArgFn = (msg: string) => void;
type MultiArgFn = (a: number, b: Uint8Array) => void;
interface AppInit {
onReady: SingleArgFn;
onData: MultiArgFn;
status: UserStatus;
}
export function execute_callbacks(callbacks: AppInit): void;
```
### Example: Returning a Value
```rust
#[ts]
pub type CalculateFn = fn(a: f64) -> f64;
// Usage:
let result: f64 = calculate_fn.call(10.0).unwrap();
```
## Supported Types
`ts-function` supports a wide range of Rust types, automatically mapping them to their idiomatic TypeScript equivalents.
| `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](#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:
```rust
#[ts]
pub struct BasicInfo {
pub name: String,
pub age: f64,
}
// In your wasm boundary:
let info = BasicInfo {
name: "Alice".to_string(),
age: 30.0,
};
// Converts 1-to-1 to a Javascript Object: `{ name: "Alice", age: 30.0 }`
let js_val: JsValue = info.into();
```
### Deferred Parsing
When you accept a `#[ts]` struct like `AppInit` as an argument, `wasm-bindgen` automatically parses and clones the JavaScript object properties into the Rust struct across the Wasm boundary. If your struct is very large or you only need to access a single property conditionally, this full cloning might be a performance bottleneck.
To solve this, `ts-function` additionally generates an `I`-prefixed JS binding (e.g., `IAppInit` for `AppInit`). You can accept this `I`-prefixed type in your `#[wasm_bindgen]` signatures to accept a raw `JsValue` reference instead.
This allows you to either access properties dynamically via the generated getters (e.g., `cbs.status()`), or conditionally do a full Rust struct conversion using the generated `.parse()` method:
```rust
#[wasm_bindgen]
pub fn execute_callbacks_deferred(cbs: IAppInit) {
// We only want to run the expensive parse if the user is active
if cbs.status() == UserStatus::Active {
// `.parse()` extracts all properties from JS into your `AppInit` struct
let callbacks: AppInit = cbs.parse();
callbacks.on_ready.call("System is ready".to_string()).unwrap();
}
}
```
### 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:
```rust
#[ts]
pub type LargeDataFn = fn() -> js_sys::Uint8Array;
// Usage (Zero-Copy):
let arr: js_sys::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!**
```rust
use wasm_bindgen::prelude::*;
use ts_function::ts;
// 1. Define your own tuple struct
pub struct CustomLoggingFn(pub js_sys::Function);
// 2. Apply the macro to the impl block
#[ts]
impl CustomLoggingFn {
// 3. Define the `call` signature exactly how you want it exposed to TypeScript
pub fn call(&self, val: f64) {
// You are responsible for all conversions and calling the JS function
let result = self.0.call1(
&wasm_bindgen::JsValue::NULL,
&wasm_bindgen::JsValue::from_f64(val),
);
// Handle errors internally however you want
if let Err(e) = result {
if let Ok(err_obj) = e.dyn_into::<js_sys::Error>() {
web_sys::console::error_1(&err_obj.message().into());
}
}
}
}
```
## License
Dual-licensed under either the [MIT license](LICENSE-MIT) or the [Apache License, Version 2.0](LICENSE-APACHE) at your option.