rustwebassembly

How to return a string (or similar) from Rust in WebAssembly?


I created a small Wasm file from this Rust code:

#[no_mangle]
pub fn hello() -> &'static str {
    "hello from rust"
}

It builds and the hello function can be called from JS:

<!DOCTYPE html>
<html>
<body>
  <script>
    fetch('main.wasm')
    .then(response => response.arrayBuffer())
    .then(bytes => WebAssembly.instantiate(bytes, {}))
    .then(results => {
      alert(results.instance.exports.hello());
    });
  </script>
</body>
</html>

My problem is that the alert displays "undefined". If I return a i32, it works and displays the i32. I also tried to return a String but it does not work (it still displays "undefined").

Is there a way to return a string from Rust in WebAssembly? What type should I use?


Solution

  • WebAssembly only supports a few numeric types, which is all that can be returned via an exported function.

    When you compile to WebAssembly, your string will be held in the module's linear memory. In order to read this string from the hosting JavaScript, you need to return a reference to its location in memory, and the length of the string, i.e. two integers. This allows you to read the string from memory.

    You use this same technique regardless of whichever language you are compiling to WebAssembly. How can I return a JavaScript string from a WebAssembly function provides a detailed background to the problem.

    With Rust specifically, you need to make use of the Foreign Function Interface (FFI), using the CString type as follows:

    use std::ffi::CString;
    use std::os::raw::c_char;
    
    static HELLO: &'static str = "hello from rust";
    
    #[no_mangle]
    pub fn get_hello() -> *mut c_char {
        let s = CString::new(HELLO).unwrap();
        s.into_raw()
    }
    
    #[no_mangle]
    pub fn get_hello_len() -> usize {
        HELLO.len()
    }
    

    The above code exports two functions, get_hello which returns a reference to the string, and get_hello_len which returns its length.

    With the above code compiled to a wasm module, the string can be accessed as follows:

    const res = await fetch('chip8.wasm');
    const buffer = await res.arrayBuffer();
    const module = await WebAssembly.compile(buffer);
    const instance = await WebAssembly.instantiate(module);
    
    // obtain the module memory
    const linearMemory = instance.exports.memory;
    
    // create a buffer starting at the reference to the exported string
    const offset = instance.exports.get_hello();
    const stringBuffer = new Uint8Array(linearMemory.buffer, offset,
      instance.exports.get_hello_len());
    
    // create a string from this buffer
    let str = '';
    for (let i=0; i<stringBuffer.length; i++) {
      str += String.fromCharCode(stringBuffer[i]);
    }
    
    console.log(str);
    

    The C equivalent can be seen in action in a WasmFiddle.