Search code examples
gorustwebassemblyrust-wasmwasmtime

How do I call a Rust function from Go with a string as a parameter?


I have been trying to pass a string to a Rust function (compiled to Wasm), however for what I understood, right now there is no way to pass strings directy, because the "str" is not a type in the "FFI world" (at least that's what the rust compiler is saying): = help: consider using `*const u8` and a length instead

So what I did was changing the function to this form (instead of using a simple &str type):

#[no_mangle]
pub extern "C" fn greet(s: *mut u8, len: usize) {
    let s = std::str::from_utf8(unsafe { std::slice::from_raw_parts(s, len) }).unwrap();
    println!("Hello, {}!", s)
}

This means that I need a pointer and the length of the string in u8.

However, someone made me notice that WASM modules are sandboxed, so they can't use normal pointers like normal applications. Thus, I have to use a function like this one to allocate memory into the module's linear memory:

use std::alloc::{alloc, dealloc, Layout};
#[no_mangle]
pub unsafe fn my_alloc(len: usize) -> *mut u8 {
    let align = std::mem::align_of::<usize>();
    let layout = Layout::from_size_align_unchecked(size, align);
    alloc(layout)
}

This is an example of a JS function that uses an alloc function like this one:

function copyMemory(data, instance) {
  var ptr = instance.exports.alloc(data.length);
  var mem = new Uint8Array(instance.exports.memory.buffer, ptr, data.length);
  mem.set(new Uint8Array(data));
  return ptr;
}

My problem is that I don't know how to convert this function to Go, that's because I am stuck at the "var mem" line, for these reasons:

  • I can't find the equivalent of "instance.exports.memory.buffer" in Go (instance is a "*wasmtime.Instance" type).
  • I don't know how to do what Unit8Buffer does in Go.

Good read about memory in Wasm: (https://radu-matei.com/blog/practical-guide-to-wasm-memory/#exchanging-strings-between-modules-and-runtimes)


Solution

  • Took me a little to understand exactly how the wasmtime Go-package worked, but in the end I resolved my problem doing this:

    func main() {
        dir, err := ioutil.TempDir("", "out")
        check(err)
        defer os.RemoveAll(dir)
        stdoutPath := filepath.Join(dir, "stdout")
    
        engine := wasmtime.NewEngine()
        store := wasmtime.NewStore(engine)
    
        linker := wasmtime.NewLinker(store)
    
        // Configure WASI imports to write stdout into a file.
        wasiConfig := wasmtime.NewWasiConfig()
        wasiConfig.SetStdoutFile(stdoutPath)
    
    
        wasi, err := wasmtime.NewWasiInstance(store, wasiConfig, "wasi_snapshot_preview1")
        check(err)
    
        // Link WASI
        err = linker.DefineWasi(wasi)
        check(err)
    
        // Create our module
        module, err := wasmtime.NewModuleFromFile(store.Engine, "wasm_file.wasm")
        check(err)
        instance, err := linker.Instantiate(module)
        check(err)
    
    
        fn := instance.GetExport("greet").Func()
        memory := instance.GetExport("memory").Memory()
        alloc := instance.GetExport("my_alloc").Func()
    
        // // string for alloc
        size2 := int32(len([]byte("Elvis")))
    
        // //Allocate memomory
        ptr2, err := alloc.Call(size2)
        pointer, _ := ptr2.(int32)
    
        buf := memory.UnsafeData()
        for i, v := range []byte("Elvis") {
            buf[pointer+int32(i)] = v
        }
    
        // Use string func
        _, err = fn.Call(pointer, size2 )
        check(err)
        
        // Print WASM stdout
        out, err := ioutil.ReadFile(stdoutPath)
        check(err)
        fmt.Print(string(out))
    }