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/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/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..1f9dcd74 --- /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: *mut 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_mut(), + 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); + } +}