From d493001feaf65d4bef1883fff3275ce1ebf415bc Mon Sep 17 00:00:00 2001 From: Junji Takakura Date: Sat, 21 Mar 2026 09:15:21 +0900 Subject: [PATCH 1/2] Add Rust FFI example using zwasm C API --- .gitignore | 4 +++ examples/rust/Cargo.toml | 7 ++++ examples/rust/build.rs | 16 +++++++++ examples/rust/src/main.rs | 68 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 examples/rust/Cargo.toml create mode 100644 examples/rust/build.rs create mode 100644 examples/rust/src/main.rs diff --git a/.gitignore b/.gitignore index 861ae777..2ebe2ec8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,10 @@ zig-cache/ # Python __pycache__/ +# Rust +target/ +Cargo.lock + # Editor *.swp *~ diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml new file mode 100644 index 00000000..ba475ad5 --- /dev/null +++ b/examples/rust/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "rust-example" +version = "0.1.0" +edition = "2024" +build = "build.rs" + +[dependencies] diff --git a/examples/rust/build.rs b/examples/rust/build.rs new file mode 100644 index 00000000..41c1d2c8 --- /dev/null +++ b/examples/rust/build.rs @@ -0,0 +1,16 @@ +use std::{env, path::PathBuf}; + +fn main() { + let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let lib_dir = manifest + .join("..") + .join("..") + .join("zig-out") + .join("lib") + .canonicalize() + .expect("zig-out/lib not found — run `zig build lib` first"); + + println!("cargo:rustc-link-search=native={}", lib_dir.display()); + println!("cargo:rustc-link-lib=zwasm"); + println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir.display()); +} diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs new file mode 100644 index 00000000..4b53be5b --- /dev/null +++ b/examples/rust/src/main.rs @@ -0,0 +1,68 @@ +//! main.rs — Minimal example using the zwasm C API via FFI +//! +//! Demonstrates: load module, invoke exported function, read result. +//! +//! Build: zig build lib && cargo build +//! Run: cargo run + +#[repr(C)] +struct zwasm_module_t { + _private: [u8; 0], +} + +#[link(name = "zwasm")] +unsafe extern "C" { + fn zwasm_module_new(wasm_ptr: *const u8, len: usize) -> *mut zwasm_module_t; + fn zwasm_module_invoke( + module: *mut zwasm_module_t, + name: *const std::ffi::c_char, + args: *const u64, + nargs: u32, + results: *mut u64, + nresults: u32, + ) -> bool; + fn zwasm_module_delete(module: *mut zwasm_module_t); + fn zwasm_last_error_message() -> *const std::ffi::c_char; +} + +/* Wasm module: export "f" () -> i32 { return 42 } */ +const WASM: &[u8] = &[ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f, 0x03, + 0x02, 0x01, 0x00, 0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00, 0x0a, 0x06, 0x01, 0x04, 0x00, 0x41, + 0x2a, 0x0b, +]; + +fn main() { + unsafe { + let module = zwasm_module_new(WASM.as_ptr(), WASM.len()); + + if module.is_null() { + let err_msg = zwasm_last_error_message(); + let c_str = std::ffi::CStr::from_ptr(err_msg); + eprintln!("Failed to create module: {}", c_str.to_str().unwrap()); + return; + } + + let name = std::ffi::CString::new("f").unwrap(); + let mut results = [0u64; 1]; + let ok = zwasm_module_invoke( + module, + name.as_ptr(), + std::ptr::null(), + 0, + results.as_mut_ptr(), + results.len() as u32, + ); + + if !ok { + let err_msg = zwasm_last_error_message(); + let c_str = std::ffi::CStr::from_ptr(err_msg); + eprintln!("Invoke error: {}", c_str.to_str().unwrap()); + zwasm_module_delete(module); + return; + } + + println!("f() = {}", results[0]); + zwasm_module_delete(module); + } +} From 29614a0925b5c7b5423ed95d78201773b0bc3b21 Mon Sep 17 00:00:00 2001 From: Junji Takakura Date: Sat, 21 Mar 2026 09:19:31 +0900 Subject: [PATCH 2/2] Update README: add Rust example section --- README.md | 4 ++++ examples/rust/src/main.rs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1cda336b..c1840bf8 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,10 @@ C example using `libzwasm`: load a module, invoke an export, read the result. Python ctypes example: same workflow as C, no compiled bindings needed. +### Rust examples (`examples/rust/`) + +Rust FFI example: same workflow as C, using `extern "C"` bindings. + ## Build Requires Zig 0.15.2. diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index 4b53be5b..1f9dcd74 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -16,7 +16,7 @@ unsafe extern "C" { fn zwasm_module_invoke( module: *mut zwasm_module_t, name: *const std::ffi::c_char, - args: *const u64, + args: *mut u64, nargs: u32, results: *mut u64, nresults: u32, @@ -48,7 +48,7 @@ fn main() { let ok = zwasm_module_invoke( module, name.as_ptr(), - std::ptr::null(), + std::ptr::null_mut(), 0, results.as_mut_ptr(), results.len() as u32,