Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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?

like image 314
rap-2-h Avatar asked Nov 28 '17 10:11

rap-2-h


People also ask

Is Rust good for WebAssembly?

The State of WebAssembly 2022 survey of Wasm developers shows Rust on top, with Blazor and Python on the rise. The Rust programming language is the most frequently used language for developing WebAssembly applications, according to a recent survey. And WebAssembly is growing in popularity.

Is Rust Wasm faster than JS?

In one recent study, a developer discovered Wasm is faster than JavaScript across three desktop browsers on desktop computers and smartphones. Wasm is 1.15-1.67 times faster than JavaScript on Google Chrome on a desktop. Wasm is 1.95-11.71 times faster than JavaScript on Firefox on a desktop.

What is Wasm-Bindgen?

The wasm-bindgen tool is sort of half polyfill for features like the host bindings proposal and half features for empowering high-level interactions between JS and wasm-compiled code (currently mostly from Rust).

How does Rust compile to Wasm?

Building the packageCompiles your Rust code to WebAssembly. Runs wasm-bindgen on that WebAssembly, generating a JavaScript file that wraps up that WebAssembly file into a module the browser can understand. Creates a pkg directory and moves that JavaScript file and your WebAssembly code into it. Reads your Cargo.


2 Answers

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.

like image 123
ColinE Avatar answered Oct 13 '22 01:10

ColinE


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

like image 40
letmutx Avatar answered Oct 13 '22 02:10

letmutx