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

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.


You cannot directly return a Rust String or an &str. Instead allocate and return a raw byte pointer containing the data which has to be then encoded as a JS string on the JavaScript side.

You can take a look at the SHA1 example here.

The functions of interest are in

  • demos/bundle.js - copyCStr
  • demos/sha1/sha1-digest.rs - digest

For more examples: https://www.hellorust.com/demos/sha1/index.html