Building a Web Assembly powered password generator

Building a Web Assembly powered password generator

- 21 mins

In this tutorial, we will be building a simple npm package with Rust and Web Assembly and testing it out on a React application.

Some context

Over the past few years, a popular trend in web development is emerging where developers write code in low level languages and compile it to a format that can be executed on the browser alongside JavaScript. That’s the high-level concept behind Web Assembly.

But What exactly is it and why use the Rust programming language?

Web Assembly (wasm) is a simple machine model and executable format with an extensive specification. It is designed to be portable, compact and execute at or near native speeds and is a compilation target for languages such as Rust, C, C++ and more.

Rust, on the other hand, is a blazingly fast systems language which gives developers low-level control and reliable performance. It lacks a garbage collector which slows down high-level languages like JavaScript.

The benefit of using Rust with Web Assembly is just how simple the developer experience is! Integration with and support for popular tools in the JavaScript ecosystem such as npm and web-pack make it very developer friendly.

To demonstrate this, we will be building a simple password generator program in Rust, compiling that code into Web Assembly and publishing the resulting package to npm to be used by other developers. As a bonus, we will also be creating a bear-bones React application which will make use of our package. By the end of this project, the interplay between these technologies will be vivid.


Initializing our Rust project with wasm-pack

Starting a Rust Web Assembly project is as simple as running the following command in your terminal:

wasm-pack new wasm_pass

The outcome of running this is basically a Cargo generated Rust library crate with web-assembly batteries included. This means we are still able to add external crates/ libraries into the generated project, something we will get to later on.

Dissecting the boilerplate

Let’s enter into our project and see what we have:

cd wasm_pass

Below is the file tree of our generated project; the most important files we will focus on are the Cargo.toml and src/

├── Cargo.toml
    └── src


The Cargo.toml file contains the dependencies of our project as well as metadata for our crate. Since our crate was generated by wasm-pack, we get wasm-bindgen already preconfigured for us.


This is root of our crate which will be ultimately compiled to Web Assembly. The preconfigured wasm_bindgen crate allows us to interface our Rust code with JavaScript by simply adding the #[wasm_bindgen] attribute to any Rust code we would like to expose to JavaScript. The existing code imports the window.alert JavaScript function and exports a greet function.


This module contains some useful debugging utilities for our compiled Web Assembly, we won’t be making use of this module during this project.

Initial build

To get a better understanding of how we compile our Rust code to Web Assembly and have it exposed to JavScript, we will first build the example Rust code generated by wasm-pack and see what we get:

wasm-pack build

When this is complete, you you should expect a pkg directory to be generated at the root of our project with the following build artefacts inside:

├── package.json
├── wasm_pass.wasm
├── wasm_pass.d.ts
└── wasm_pass.js

wasm-pack does a great job at bundling our resulting artefacts into a “npm publishable” state as you can see. The is simply a copy of our main project’s Let’s break down the rest of the files in more detail.


This is the Web Assembly binary generated by the Rust compiler from our code in src/ It simply contains all our Rust functions we had wrapped with the #[wasm_bindgen] attribute compiled to wasm!


This file is generated by wasm-bindgen and contains JavaScript code which acts as the intermediary between JavaScript and Rust by allowing importation of DOM and JavaScript functions into Rust and creating a JavaScript friendly API from our web assembly functions.

import * as wasm from './wasm_pass_bg’;

// …

export function greet() {
    return wasm.greet();


The .d.ts contains TypeScript type declarations for those who will be using TypeScript. Therefore, calls to our Web Assembly code will be type-checked and you will get nice features such as intellisense and auto-completion depending on the IDE you use.


The package.json file contains meta-data about the generated JavaScript and WebAssembly package. This helps integrate with JavaScript tooling and allow us to publish our package to npm.

    "name": "wasm-pass",
    "collaborators": ["Your Name <>"],
    "version": "0.1.0",
    "files": ["wasm_pass_bg.wasm", "wasm_pass.js", "wasm_pass.d.ts"],
    "module": "wasm_pass.js",
    "types": "wasm_pass.d.ts",
    "sideEffects": false

Writing some Rust

Adding an external crate

First thing we want to do first is add an external crate to our project.

Installing external libraries or crates, as they are referred to in the Rust community, is as easy as specifying the name and version of the library we would like to use under the dependencies column of our Cargo.toml like so:


wasm-bindgen = "0.2.63"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }

Here we add the rand crate version = "0.7.3" to our dependencies.

We also include a feature flag; features=["wasm-bindgen"] in order to get a version of the rand crate which is can be compiled into Web Assembly.

The rand crate provides utilities to generate random numbers and convert them to useful types, we will be making use of this in our password generator program.

Lets build our project to download our newly added external crate.

cargo build

Implementing our password generator logic

Now that we have the rand crate installed, we can bring it into the scope of our code in src/ All functionality provided by the crate will be accessible through the crate’s name, in this case rand. We are particularly interested in the Rng trait provided by the crate, we can access it by bringing it into scope using the use keyword:


use wasm_bindgen::prelude::*;
use rand::Rng;

pub fn generate(len: usize) -> String {

    let mut rng = rand::thread_rng();

    let password: String = (0..len)
        .map(|_| {
            let idx = rng.gen_range(0, CHARSET.len());
            CHARSET[idx] as char

To make keep things clean, I have deleted a lot of the default code generated by wasm-pack since we won’t be needing it any more. I also added a public generate function which takes a single argument len of type usize and returns a String.

If you are unfamiliar with the Rust syntax, usize is an unsigned integer type which counts how many bytes it takes to reference a location in memory.

Inside our function we create a CHARSET constant of type &[u8], simply a reference to an array of unsigned 8bit integers. In Rust, u8 integers can store numbers from 0 to 2^8 - 1, which is bascally numbers from 0 to 255. We set the value of this constant as a string slice of letters and symbols converted to a byte array through calling the method as_bytes.

We create a mutable rng variable where we make use of the rand crate’s thread_rng function to create our random number generator, an instance of the ThreadRng struct, which contains useful methods that generate random numbers for us. We then create a password variable and annotate it’s type as a String, what we expect as the final outcome of this operation.

We then make use of Rust’s functional programming features by calling the map method on a range of numbers between 0 and the user provided len value. For each number within this range, we call a closure where we declare a variable idx which contains a random generated number between 0 and the length of the CHARSET byte array, through calling the gen_range method. The closure returns the &u8 integer at the idx index of the CHARSET byte array type casted to a char type.

We finally call the collect method to transform the output from the iterator into a collection, which is a String in this case. This is what we eventually return from our function.

Testing our code

It is always best practice to test code before we ship it, let’s go ahead and write a simple unit-test for our generate function. Feel free to delete the default tests/ directory generated by wasm-pack at this point since it is redundant to us now.

We will write our tests in the same module where our generate function is declared, a popular convention in Rust:


//...our function

mod tests {
    use super::generate;
    fn test_generate() {
        let password = generate(20);
        println!("{}", password);
        assert_eq!(password.len(), 20);

We first create a tests module using the mod keyword and use the #[cfg(test)] attribute to basically to have this module only compiled when we run tests. Inside the module, we import our generate function from the outer scope.

We write a test function test_generate, which we mark with the #[test] attribute to inform the test runner to treat this function as a test. Inside our function, we declare a password variable which we assign to the result of a call to our generate function, where we pass 20 as a parameter, since we want to generate a password with twenty characters.

We then write a print statement with Rust’s println! macro so we can see our generated password printed on the standard output. Finally, we use another Rust macro assert_eq! to assert that the length of the password which is generated by our function is equal to 20.

Now we can run our tests:

cargo test -- --show-output

Note that we add the --show-output flag, in order to be able to see what is printed into the standard output as our tests run.

When we run the tests, we should see the following output:

running 1 test

test tests::test_generate ... ok


---- tests::test_generate stdout ----




test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Our tests are successful! We are also able to see our random password printed out on the standard output! Now we can compile our Rust code into Web Assembly by running our trusty build command yet again:

wasm-pack build


Incase you run into an error message reminding you to disable wasm-opt while attempting to compile your code, you can fix it by updating your Cargo.toml with the following:


wasm-opt = false

This will disable wasm-pack Web Assembly optimization for the release profile, which is tolerable for our simple project.

You can then proceed to build your package again.

Implementing Web Assembly on a React application

We are now going to try out our new package on a bare-bones React application to see the integration of Web Assembly and modern front-end technologies at play.

In the root of our main project, we will first create a app directory, which will contain our React application, and cd into it.

mkdir app
cd app

Inside, we will create a src directory and we also create a .gitignore file with node_modules inside:

mkdir src
echo "/node_modules" > .gitignore

We then initialize an npm package with default options by running:

npm init -y

Next, we install React, Babel, webpack and a few other webpack plugins:

npm install --save react react-dom

npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader webpack webpack-cli webpack-dev-server html-webpack-plugin style-loader css-loader html-loader

We then create a index.html file at the root of our app directory and add the following:


<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <div id='root'>

In the src directory, we add an index.js, which will serve as the entry point to our React application:


import React from "react";
import ReactDOM from "react-dom";
import App from "./App.js";

ReactDOM.render(<App />, document.getElementById("root"));

We will also need to create a App.js file in the src directory which will contain our App component:


import React from "react";

const App = () => {
    return <h1>Hello from React</h1>;

export default App;

Now we can create a .babelrc file at the root of our app directory:


  "presets": ["@babel/preset-env", "@babel/preset-react"]

We proceed to add a webpack.config.js file also at the root of our app directory (don’t worry too much about it’s content):


const HtmlWebpackPlugin = require("html-webpack-plugin");

const path = require("path");

module.exports = {
    entry: "./src/index.js",
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "index.js",
    module: {
        rules: [
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader",
                test: /\.css$/,
                use: ["style-loader", "css-loader"],
                test: /\.s[ac]ss$/i,
                use: ["style-loader", "css-loader"],
                test: /\.html$/,
                use: {
                    loader: "html-loader",
    mode: "development",
    plugins: [
        new HtmlWebpackPlugin({
            template: "./index.html",

Finally, we add a start and build script to our package.json


// ...
"scripts": {
    "start": "webpack-dev-server --open",
    "build": "webpack --config webpack.config.js",
// ...

We can start our webpack server by running:

npm start

Our React app is now running on http://localhost:8080!

Using our package

Now that we have our React app working, we can test our wasm-pass package inside it. To avoid any conflicts in the name you chose for your npm package, we will use the local package in the pkg directory instead. We add it as a dependency in our package.json like so:


 "dependencies": {
    // ...
    "wasm-pass": "file:../pkg/wasm_pass"

Since we are importing our package locally, we set the version as a file referencing the location of our wasm-pack generated pkg directory content.

Finally, run npm install to have it saved in our node_modules:

npm install

Let’s now update our App component:


import React, { useState, useCallback, useEffect } from "react";

const App = () => {
    const [password, setPassword] = useState("");
    const [input, setInput] = useState("");

    const handleChange = (e) => {

    const generatePassword = useCallback(() => {
        const module = import("wasm-pass")
        module.then(({generate}) => {
        }).catch(err => {
    }, [input]);

    useEffect(() => {
    }, [input]);
    return (
            <p>Enter password length:</p>
            <input onChange={handleChange} type="number" value={input} />
            <button onClick={generatePassword}>Generate Password</button>
            <p>Your password:</p>

export default App;

There are a couple of important changes in this file. First, we import the useState, useCallback and useEffect hooks from react for simple state management, memoization and performing of side effects.

This component also contains an input field for entering the desired length of the password and a button whose click event is handled by our generatePassword callback.

We create our generatePassword callback using React’s useCallback hook to memoize the password generation logic from web assembly with the input state value as our dependency. We also import our wasm-pass package here. Notice how we use the import function rather than the regular ES6 import syntax. This is because currently, web assembly can only be loaded dynamically by the browser. This import function will return a Promise, therefore to gain access to our wasm-pass module, we use the Promise.then syntax. We alert any error we catch.

Inside this callback, we call the generate function from our wasm module which in turn updates the password state via setPassword. We also call parseInt to cast our numeric string input to a number.

Finally, we call the useEffect hook and subscribe to any changes to our input state, where we call our generatePassword function as a side effect.

The final password is displayed in a strong tag.

Let’s run our dev server once again and see what we get:

npm start


Our simple password generator is working!


There’s so much more we can do with Rust and Web Assembly, we have barely scratched the surface! This tutorial hopefully shows how we transition from Rust to JavaScript through Web Assembly, and how we can implement a Web Assembly generated npm package on a React application.

A full working implementation of this project can be found here.

Here are some good resources if you wish to learn more about Rust and Web Assembly:

Collins Muriuki

Collins Muriuki

Understanding the universe and explaining it to a three year old.

rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora keybase