catswords-jsrt-rs provides minimal ChakraCore bindings for Rust. (Experimental)
This project aims to keep all unsafe code strictly inside the low-level FFI crate, while offering a small, ergonomic, and predictable API surface for embedding ChakraCore in Rust applications.
Inspired by @darfink’s Rust wrapper, which is outdated. I rewrote it from scratch with compatibility in mind.
This project is a subproject within the WelsonJS open-source ecosystem.
This repository is a Cargo workspace with the following crates:
-
catswords-jsrt-sys- Raw FFI bindings to ChakraCore
- All
unsafecode lives here - Thin, mostly mechanical bindings to the C API
-
catswords-jsrt- Safe-ish ergonomic wrapper
- API inspired by ChakraCore samples
- Focused on correctness and minimal abstraction
-
catswords-jsrt-examples- Runnable example binaries
- Used to validate real execution paths
The chakracore crate exposes a minimal, explicit API:
Runtime::new()Context::new(&runtime)context.make_current() -> Guardscript::eval(&guard, "...")value::Function::new(&guard, closure)Function::call(&guard, &[&Value])
The Guard type enforces context lifetime and helps prevent common misuse patterns.
You need ChakraCore headers and libraries available on your system.
ChakraCore.h(header)ChakraCore.lib(import library)ChakraCore.dll(runtime)
Set these so chakracore-sys can locate ChakraCore:
$env:CHAKRACORE_INCLUDE_DIR="C:\path\to\ChakraCore\include"
$env:CHAKRACORE_LIB_DIR="C:\path\to\ChakraCore\lib"cargo buildAt runtime, ChakraCore.dll must be discoverable:
- Place it next to the produced
.exe, or - Add its directory to
PATH
ChakraCore must be available as a shared library. If your distribution provides ChakraCore:
ChakraCore.hmust be availablelibChakraCore.somust be linkable
Set environment variables if needed:
export CHAKRACORE_INCLUDE_DIR="/usr/include"
export CHAKRACORE_LIB_DIR="/usr/lib"
#export CHAKRACORE_INCLUDE_DIR="/path/to/ChakraCore/include" # build from source
#export CHAKRACORE_LIB_DIR="/path/to/ChakraCore/build/lib" # build from source
#export LD_LIBRARY_PATH="$CHAKRACORE_LIB_DIR:$LD_LIBRARY_PATH" # build from sourceBuild:
cargo buildAll runnable examples live in the catswords-jsrt-examples crate and are built as binaries.
hello_worldmultiply
> cargo run -p catswords-jsrt-examples --bin multiply
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
Running `target\debug\multiply.exe`
direct call: 191 * 7 = 1337
global eval: multiply(191, 7) = 1337
cargo run -p catswords-jsrt-examples --bin hello_world
cargo run -p catswords-jsrt-examples --bin multiplyIf you encounter a runtime error related to missing ChakraCore.dll, ensure it is either:
- In the same directory as the executable, or
- In a directory listed in
PATH
If libChakraCore.so is not in a default loader path:
export LD_LIBRARY_PATH="$CHAKRACORE_LIB_DIR:$LD_LIBRARY_PATH"Then run:
cargo run -p catswords-jsrt-examples --bin hello_world
cargo run -p catswords-jsrt-examples --bin multiplyBinary: catswords-jsrt-examples --bin hello_world
extern crate catswords_jsrt as js;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Create runtime
let runtime = js::Runtime::new()?;
// 2. Create context
let context = js::Context::new(&runtime)?;
// 3. Make context current (RAII)
let guard = context.make_current()?;
// 4. Evaluate script
let result = js::script::eval(&guard, "5 + 5")?;
// 5. Convert result
let value = result.to_integer(&guard)?;
assert_eq!(value, 10);
println!("5 + 5 = {}", value);
Ok(())
}Run:
cargo run -p catswords-jsrt-examples --bin hello_worldBinary: catswords-jsrt-examples --bin multiply
extern crate catswords_jsrt as js;
type AnyResult<T> = Result<T, Box<dyn std::error::Error>>;
fn make_multiply(guard: &js::Guard) -> js::value::Function {
js::value::Function::new(guard, Box::new(|guard, info| {
if info.arguments.len() != 2 {
return Err(js::err_msg(
js::JsErrorCode::JsErrorInvalidArgument,
format!("multiply expects 2 arguments, got {}", info.arguments.len()),
));
}
let a = info.arguments[0].to_integer(guard)?;
let b = info.arguments[1].to_integer(guard)?;
Ok(js::value::Number::new(guard, a * b).into())
}))
}
fn scenario_direct_call(guard: &js::Guard, multiply: &js::value::Function) -> AnyResult<()> {
let a: js::value::Value = js::value::Number::new(guard, 191).into();
let b: js::value::Value = js::value::Number::new(guard, 7).into();
let result = multiply.call(guard, &[&a, &b])?;
let value = result.to_integer(guard)?;
assert_eq!(value, 1337);
println!("direct call: 191 * 7 = {}", value);
Ok(())
}
fn scenario_global_eval(guard: &js::Guard, multiply: js::value::Function) -> AnyResult<()> {
let context = guard.context();
let fval: js::value::Value = multiply.into();
context.set_global("multiply", &fval)?;
let result = js::script::eval(guard, "multiply(191, 7)")?;
let value = result.to_integer(guard)?;
assert_eq!(value, 1337);
println!("global eval: multiply(191, 7) = {}", value);
Ok(())
}
fn main() -> AnyResult<()> {
let runtime = js::Runtime::new()?;
let context = js::Context::new(&runtime)?;
let guard = context.make_current()?;
let multiply = make_multiply(&guard);
scenario_direct_call(&guard, &multiply)?;
scenario_global_eval(&guard, multiply)?;
Ok(())
}Run:
cargo run -p catswords-jsrt-examples --bin multiplyI am always open. Collaboration, opportunities, and community activities are all welcome.