# dodo-bindgen
`dodo-bindgen` is an FFI bindings generator that simplifies writing Rust libraries for Dodo.
It works by providing procedural macros that create wrappers around native Rust functions and make it FFI-safe. This makes writing FFI libraries easier as it deals with boilerplate code generation so you don't have to.
It also provides a CLI tool that generates a TypeScript module and tries to follow the semantics of your code wherever possible.
This project aims to empower TypeScript development by bridging access to Rust's powerful memory-safe ecosystem.
## To use
Add the `dodo-bindgen` crate to your library crate's dependencies (note the spelling and dash `-` separator)
```toml
# Cargo.toml
[dependencies]
dodo-bindgen = "2.0"
```
Use the `dodo_bindgen` attribute macro on a function, implement, or struct item in your source code:
```rust
// lib.rs
use dodo_bindgen::dodo_bindgen;
#[dodo_bindgen]
fn test_1() {
println!("Hello, world!");
}
#[dodo_bindgen]
fn test_2(string: String) -> String {
format!("{} to Rust!", string)
}
```
Next, to generate the bindings and TypeScript code, you must install the CLI tool with the command below:
```sh
cargo install dodo-bindgen-cli
```
Then, run this command in your project's package folder (not the workspace folder).
```sh
dodo-bindgen
```
This will automatically generate a TypeScript module in `<pkg_root>/lib/<your_module>`, along with another module `rust_type.ts` that contains TypeScript representations of Rust types. Generated files are formatted with `deno fmt`, so your `deno.json` formatting options apply.
Finally, you can write your TypeScript code and import the functions from the Rust code. Function and method names are converted to camelCase (`test_1` becomes `test1`), and a static `fn new() -> Self` becomes the class constructor.
```ts
// hello_world.ts
import { RustString, test1, test2 } from "./lib/libmy_mod.ts";
test1();
Deno.test("test_string", () => {
// create a `RustString` instance from a JavaScript string
// by calling the `from()` static method
const hello_string = test2(RustString.from("Hello from Deno"));
// turn the RustString into a JavaScript string
// by calling its `into()` method
console.log(hello_string.into());
})
```
To run:
```bash
deno run --allow-all --unstable-ffi hello_world.ts
```
This should output:
```text
Hello, world!
Hello from Deno to Rust!
```
For additional code generation options, run `dodo-bindgen --help`
### Primitive types
Numeric types map to `number` (64-bit and pointer-sized integers to `bigint`), `bool` to `boolean` and `char` to a single-character `string`.
### JavaScript strings
`String` values cross the boundary as `RustString` handles. To work with plain JavaScript strings instead, use `JsStr` (borrowed, parameters only) and `JsString` (owned, parameters and return values):
```rust
#[dodo_bindgen]
fn greet(name: JsStr) -> JsString {
format!("Hello, {name}!").into()
}
```
```ts
greet("Deno"); // "Hello, Deno!"
```
### Typed arrays
JavaScript typed arrays map to `JsSlice<T>` (borrowed, zero-copy), `JsSliceMut<T>` (borrowed mutably, zero-copy, writes are visible to JavaScript) and `JsVec<T>` (owned; copied from parameters, returned as a fresh typed array). `T` is one of `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `f32` or `f64`:
```rust
#[dodo_bindgen]
fn scale(mut values: JsSliceMut<f32>, factor: f32) {
for value in values.iter_mut() {
*value *= factor;
}
}
#[dodo_bindgen]
fn range(len: u32) -> JsVec<u32> {
(0..len).collect()
}
```
```ts
const values = new Float32Array([1, 2, 3]);
scale(values, 2); // values is now [2, 4, 6]
range(3); // Uint32Array [0, 1, 2]
```
Buffers passed to `non_blocking` functions are kept alive until the returned promise settles.
### Extending foreign types
Types from other crates can be exposed as TypeScript classes with a `foreign` impl block. The block is turned into a private extension trait, so the orphan rule does not apply. Concrete instances of generic types work too:
```rust
#[dodo_bindgen(foreign, name = "Bytes")]
impl Vec<u8> {
fn new() -> Self {
Vec::new()
}
fn len(&self) -> usize {
self.len()
}
}
```
Every signature using `Vec<u8>` is then typed as `Bytes` in TypeScript. `RustString` itself is defined this way inside `dodo-bindgen`. Foreign classes that no exported item refers to are left out of the generated module.
### Custom TypeScript
Additional TypeScript class members can be attached to any exported struct, enum or impl block:
```rust
#[dodo_bindgen(ts = "override toString(): string { return this.into(); }")]
#[dodo_bindgen(ts = include_str!("my_class.ts"))]
```
Every generated class also implements `[Symbol.dispose]`, so instances can be released with `using`.
## Examples
Check out the [test directory](example/) to see some examples