Rust
Rust binds the way C++ headers do: one import, no proc-macros, no hand-written glue. Bring in a crate straight from crates.io with the cargo: scheme, or write Rust next to your other native sources and import the file.
Setup
A Rust toolchain (cargo plus the platform targets) has to be installed - cargo doubles as the incremental cache, so unchanged code rebuilds as a no-op. The binding layer is a single dev dependency, and bundler plugins already depend on it, so most projects get it transitively:
platform: 'wasi' skips Rust entirely - there is no wasm32-wasip3 Rust target yet. And the wasm mt runtime needs nightly, because std is rebuilt with the atomics features: run rustup toolchain install nightly --component rust-src once.Import a crate directly
Declare the crate in cppjs.config.js, then import it with the cargo: prefix - the prefix names the store, the way node: does. Importing an undeclared crate is a hard error, not a silent miss.
The crate's own sources are read - module trees, pub use re-exports, enabled feature gates - and the bridge is generated from what is found there. You write no Rust at all.
Import a local .rs file
Rust can also sit beside your C++ under src/native and be imported like a header. Upstream crates it uses go into the same cargoDependencies map.
What plain Rust maps to
| Rust | JavaScript |
|---|---|
struct + impl methods | class with methods (Type::new becomes the constructor) |
&str / &String parameters, String returns | strings |
i32 / f64 / bool | number / boolean |
i64 / u64 | BigInt, both directions |
Option<T> | null / undefined ↔ None |
Result<T, E> returns | throws an Error on Err |
impl Display | toString() |
free pub fn | plain exported function |
&OtherClass parameters | pass the other class's instance |
serde_json::Value | real JS values, deep-copied at the boundary |
Arc<Class> | shared ownership across several JS handles |
embind_rs::JsValue / JsFunction | live JS values by identity, and callbacks into JS |
st on the main thread. Functions cannot cross a worker boundary and identity does not survive structured cloning, so on worker-backed runtimes - the mt default, or useWorker: true - use serde_json::Value instead.Publish a crate as a package
A whole crate can ship as a package: set export.type: 'cargo' and cargo build --release --target <triple> runs per platform, staging the static library like any other prebuilt. Consumers import the package name exactly as they would a C++ one.
TypeScript
Generated declarations live under .cppjs/, never in your source tree, and @cpp.js/typescript-config wires them. One caveat: when your own tsconfig defines include, it overrides rather than merges - keep .cppjs/rust-crates/types/**/*.d.ts in yours.
The same dts: 'promise' note from C++ bindings applies here.