Skip to content

Rust Wasm Trail

Recently, I got interested in the WASM as it enables you to write code by rust but run it in the browser. At the first glance, I feel it greatly benefits the case that could be done in the client side, but due to the implementation, it still runs on the server side. It has two big benefits:

  • save server resources
  • implement in a strong type language with many powerful libraries and run it in the browser
  • more portable and convenient

This especially for small tool sites as it usually requires some computation resource, but not many. This blog records my experience to trail the rust wasm for my small tool. It's not a blog about what is wasm and the technical designs/implementations of rust wasm.

Word Definition Generator App

My small app is called wordgen and it serves at a github page. The motivation is to improve my accuracy of word pronunciation. Memorizing words through a whole year, I can recognize more than 14k+ words and read books. However, the words pronunciation is the big trouble now as many words I cannot articulate them correctly. Lacking system English learning during my childhood and learning German at the first year in the uni contributes my trouble a lot. So I decide to write this tool, to print the pronunciation and the other verbal data.

The logic is very simple, read the file, and then parse it to call the webster dictionary api, and finally it generates an html page with table.

The problems are:

  • the format of glue code in rust to be triggered in the js
  • how to make http request
  • how to deploy and use it

I use wasm-pack binary tool to build my rust code into wasm format. To use it directly in browser, flag --target web is required. What's more, to escape from the CORS limitations, we need to start a http proxy server in the project path. Additionally, use relative format ./ to import the other javascript files.

http-server -c-1 .

I use library wasm-bindgen for wasm development. It provides a lot of examples for new people.

In the rust code, I exported the process method as below as a JS promise. To be honest, I'm not sure whether returning a promise or synchronized result is better.

#[wasm_bindgen]
pub async fn process(input: &[u8]) -> js_sys::Promise {
    let input = input.to_owned();
    future_to_promise(async move {
        let words: Vec<&str> = std::str::from_utf8(&input).unwrap().split(",").collect();
        let mut result: Vec<Word> = vec![];
        for word in words.iter() {
            let details = get_response(&word).await;
            result.push(details);
        }

        let output = emit_output(result).into_bytes();
        to_value(&output)
        .map_err(|err| err.into())
    })
}

After generating by running wasm-pack build --target web, the generated folder pkg looks like this:

.
├── README.md
├── package.json
├── wordgen.d.ts
├── wordgen.js
├── wordgen_bg.wasm
└── wordgen_bg.wasm.d.ts

The exported process signature is declared inside wordgen.d.ts.

export function process(input: Uint8Array): Promise<Promise<any>>;
But the implementation of process is inside wordgen.js:
/**
* @param {Uint8Array} input
* @returns {Promise<Promise<any>>}
*/
export function process(input) {
    const ptr0 = passArray8ToWasm0(input, wasm.__wbindgen_malloc);
    const len0 = WASM_VECTOR_LEN;
    const ret = wasm.process(ptr0, len0);
    return takeObject(ret);
}
To use it, we can import this file:
import init, { process } from './pkg/wordgen.js';

It's very simple to compile rust code into js by wasm and integrate them into the frontend ecosystem. However, as wasm cannot IO due to the browser engine, how could we do IO inside rust code?

Technically, it requires to trigger the js to make IO, but luckily many libraries support wasm in different degrees. So we can send the http request through reqwest library directly.

However, inside wasm, reqwest doesn't support to send synchronized request. The trouble is that the vsc doesn't complain to you, but the wasm-pack will and left you confused.

As a result, we need to use asynchronized function with await to send the request.

async fn get_response(w: &str) -> Word {
    let req_url = format!(
        "https://www.dictionaryapi.com/api/v3/references/collegiate/json/{}?key={}",
        w,"token" 
    );

    let resp = reqwest::get(&req_url).await.unwrap().text().await.unwrap();

    new_word(w.to_string(),resp).unwrap()
}

By the way, due to some limitation, the main.rs and lib.rs cannot appear together, otherwise the lib.rs will fail to import some crate under this project.

The deployment is simple as it's just a static page and all process logics are done by the js created through wasm.