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?
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
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:The above code exports two functions,
get_hello
which returns a reference to the string, andget_hello_len
which returns its length.With the above code compiled to a wasm module, the string can be accessed as follows:
The C equivalent can be seen in action in a WasmFiddle.
Most examples I saw copy the string twice. First on the WASM side, into
CString
or by shrinking theVec
to its capacity, and then on the JS side while decoding the UTF-8.Given that we often use WASM for the sake of the [loading] speed, I sought to implement a version that would reuse the Rust vector.
And the JavaScript part:
The trade-off here is that we're using
HashMap
in the WASM which might increase the size unlessHashMap
is already required.An interesting alternative would be to use the tables to share the (payload, length, capacity) triplet with the JavaScript and get it back when it is time to free the string. But I don't know how to use the tables yet.
P.S. Sometimes we don't want to allocate the
Vec
in the first place.In this case we can move the memory tracking to JavaScript: