Wrenlift

Embed in Rust

Add the runtime as a crate, instantiate a VM, hand it source. Foreign classes give Wren scripts a typed door into your Rust code.

Add the dependency

[dependencies]
wren_lift = { git = "https://github.com/wrenlift/WrenLift.git", features = ["host"] }

Pin a tag with tag = "v0.1.19" or a commit with rev = "..." for reproducible builds.

Cargo features

FeatureWhat it pulls in
host (default)Filesystem reads, plugin loading via libloading, the hatch installer. Drop for pure-embedded uses.
aotThe Cranelift ObjectModule pipeline used by wlift --aot. Needed only if you call AOT from your own binary.
craneliftThe JIT backend. On by default; opt out for an interpreter-only build.

Run a script

use wren_lift::runtime::vm::{VM, VMConfig, ExecutionMode, InterpretResult};

let mut vm = VM::new(VMConfig {
    execution_mode: ExecutionMode::Tiered,
    ..VMConfig::default()
});
vm.output_buffer = Some(String::new());

let result = vm.interpret("main", r#"
    System.print("hello from rust")
"#);

assert!(matches!(result, InterpretResult::Success));
println!("script output: {}", vm.take_output());

The first argument to interpret is the module name — it shows up in error backtraces and is the import "modname" key from other modules.

Expose a foreign class

Foreign classes give Wren scripts a typed door into your Rust code. Declare the Wren side with the #!native attribute:

#!native = "myapp"
foreign class Counter {
  #!symbol = "myapp_counter_new"
  foreign static create(start)
  #!symbol = "myapp_counter_next"
  foreign static next(handle)
}

And the matching Rust exports:

use wren_lift::ffi::{Value, WrenVm};

#[no_mangle]
pub unsafe extern "C" fn myapp_counter_new(vm: *mut WrenVm) {
    let start = wren_lift::ffi::slot(vm, 1).as_num().unwrap_or(0.0) as i64;
    let handle = MY_COUNTERS.insert(start);
    wren_lift::ffi::set_return(vm, Value::num(handle as f64));
}

#[no_mangle]
pub unsafe extern "C" fn myapp_counter_next(vm: *mut WrenVm) {
    let handle = wren_lift::ffi::slot(vm, 1).as_num().unwrap_or(0.0) as usize;
    let n = MY_COUNTERS.bump(handle);
    wren_lift::ffi::set_return(vm, Value::num(n as f64));
}

The wlift_abi crate (re-exported through wren_lift::ffi) provides the C-shaped surface you import: slot reads an argument, set_return writes the result, alloc_string / alloc_list / alloc_map create Wren-side objects.

Plugins (cdylib)

For dynamic loading (the same shape every @hatch: plugin uses), build your crate as a cdylib and ship it next to a hatchfile:

[lib]
crate-type = ["cdylib"]

[dependencies]
wlift_abi = { git = "https://github.com/wrenlift/WrenLift.git" }

Further reading