Rust by Example -- Extended Edition

Rust is a modern systems programming language focusing on safety, speed, and concurrency. It accomplishes these goals by being memory safe without using garbage collection. A big part of Rust's strength and success comes from the large ecosystem of third party libraries, known as crates.

Rust by Example -- Extended Edition (RBEext) is a collection of runnable examples that illustrate how to use popular Rust third party libraries and crates. It is designed to complement the official Rust by Example (RBE) book that focuses on the core language and standard libraries. Additionally for the curious, you can also check out the source code for this site.

Note: Many examples in this book are directly runnable from the web page. See the animation below this note. However, the Rust playground only supports 100 3rd party crates. For crates or features that are not supported by the playground, we show the results next to the examples, and link to cargo project source code.

Run an example from the web page

Now let's begin!

  • Serialization - Serialization and deserialization are key to data exchange between Rust programs and the rest of the world. The serde crate is the de facto standard here.

  • Random numbers - It is surprisingly difficult to get high quality random numbers for your application. The rand crate can help.

  • SSL/TLS toolkit - Rust API wrappers for the OpenSSL library to handle public key infrastructure and secure communications. Encryption, decryption, digital certificates, digital signature, secure digest, secure network protocols, and more!

  • N-dimensional array - Multi-dimensional arrays are crucial for scientific computing, data mining, machine learning (ML), and artificial intelligence (AI) applications.

  • Lazy initialization - Lazy initialization allows you to assign values to static variables at runtime.

  • Regular expression - Processing and manipulating text and string values.

  • WebAssembly - It is very popular to run Rust apps in WebAssembly, learn why and how.

Serialization and deserialization

Serialization is a core language feature in the era of web applications. When one program needs to talk to another program over the Internet, it needs to serialize its data into a format that can be transmitted through the network. The receiving program uses deserialization to reconstruct the data.

In Rust, most applications use the serde crate to manage serialization and deserialization. In this chapter, we will cover how to serialize typed Rust data into JSON strings or byte arrays. We will also discuss how to serialize third party structs in libraries.

Checkout the official documentation for the serde crate.

Serialize into JSON strings

To use the serde crate, you just need to add the following dependencies to your Cargo.toml file.

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

The example below shows how to serialize a simple Rust primitive data type i32 into a JSON string, and then deserialize it back. Run it! You can pass this JSON string to other Rust programs or Internet applications written in other languages.

extern crate serde;

fn main() {
    let x: i32 = 5;
    let xs = serde_json::to_string(&x).unwrap();
    println!("i32 number {} serializes into string {}", x, xs);
    let xd: i32 = serde_json::from_str(&xs).unwrap();
    assert_eq!(x, xd);
}

Here are more examples showing the serialization and deserialization of Rust primitive data types. Rust arrays and tuples are all serialized to JSON arrays. Edit the code below to try more Rust types, and run it to see the results.

extern crate serde;

fn main() {
    let x: i32 = 5;
    let xs = serde_json::to_string(&x).unwrap();
    println!("i32 number {} serializes into string {}", x, xs);
    let xd: i32 = serde_json::from_str(&xs).unwrap();
    assert_eq!(x, xd);

    let x: f32 = 3.14;
    let xs = serde_json::to_string(&x).unwrap();
    println!("f32 number {} serializes into string {}", x, xs);

    let x: Vec<u8> = vec![1, 2, 3];
    let xs = serde_json::to_string(&x).unwrap();
    println!("Vec<u8> {:?} serializes into string {}", x, xs);
    let xd: Vec<u8> = serde_json::from_str(&xs).unwrap();
    assert_eq!(x, xd);

    let x: Vec<f32> = vec![3.141, 2.718, 1.618];
    let xs = serde_json::to_string(&x).unwrap();
    println!("Vec<f32> {:?} serializes into string {}", x, xs);
    
    let x: (i32, &str, f32, bool) = (1, "hello", 4.5, true);
    let xs = serde_json::to_string(&x).unwrap();
    println!("tuple {:?} serializes into string {}", x, xs);
    let xd: (i32, &str, f32, bool) = serde_json::from_str(&xs).unwrap();
    assert_eq!(x, xd);

    let x = ((1u8, 2u16), (3.141f32, 'a'), true);
    let xs = serde_json::to_string(&x).unwrap();
    println!("nested tuple {:?} serializes into string {}", x, xs);
}

What about structs and other custom Rust data types? Well, you just need to annotate them and they will automagically get serialization capabilities! Run the example below and you can see the JSON string representation of these Rust structs.

extern crate serde;

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
  x: f32,
  y: f32
}

#[derive(Serialize, Deserialize, Debug)]
struct Line {
  points: Vec<Point>,
  valid: bool,
  length: f32,
  desc: String
}

fn main() {
    let point1: Point = Point {x:1.0, y:2.0};
    let point2: Point = Point {x:3.0, y:4.0};
    let point1s = serde_json::to_string(&point1).unwrap();
    let point2s = serde_json::to_string(&point2).unwrap();
    println!("struct Point serializes into string {}", point1s);
    println!("struct Point serializes into string {}", point2s);

    let length = ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt();
    let valid = if length == 0.0 { false } else { true };
    let line = Line { points: vec![point1, point2], valid: valid, length: length, desc: "a thin line".to_string() };
    let lines = serde_json::to_string(&line).unwrap();
    println!("struct Line serializes into string {}", lines);

    let lined: Line = serde_json::from_str(&lines).unwrap();
    assert_eq!(lined.desc, "a thin line");
    assert_eq!(lined.points[1].x, 3.0);
}

Serialize into binary

JSON strings are portable across almost all programming languages and frameworks. But for communication between Rust programs, a binary format could be much more efficient. Here is where bincode comes into play. To use the bincode crate, you just need to add the following dependencies to your Cargo.toml file.

[dependencies]
serde = { version = "1.0", features = ["derive"] }
bincode = "1.2.1"

Note: Since the bincode crate is not included the Rust Playground by default, the code examples in this article are not interactive. We will show the program output in the text. You can run these examples from this cargo project.

The example below shows how to serialize a simple Rust primitive data type i32 into a byte array, and then deserialize it back. You can pass this byte array to other Rust programs over the Internet.

use bincode;

fn main() {
    let x: i32 = 5;
    let xs: Vec<u8> = bincode::serialize(&x).unwrap();
    println!("i32 number {} serializes into byte array {:?}", x, xs);
    let xd: i32 = bincode::deserialize(&xs).unwrap();
    assert_eq!(x, xd);
}

Result

i32 number 5 serializes into byte array [5, 0, 0, 0]

Here are more examples showing the serialization and deserialization of Rust primitive data types. Edit the code below to try more Rust types, and run it to see the results.

use bincode;

fn main() {
    let x: i32 = 5;
    let xs: Vec<u8> = bincode::serialize(&x).unwrap();
    println!("i32 number {} serializes into byte array {:?}", x, xs);
    let xd: i32 = bincode::deserialize(&xs).unwrap();
    assert_eq!(x, xd);

    let x: f32 = 3.14;
    let xs = bincode::serialize(&x).unwrap();
    println!("f32 number {} serializes into byte array {:?}", x, xs);

    let x: Vec<u8> = vec![1, 2, 3];
    let xs = bincode::serialize(&x).unwrap();
    println!("Vec<u8> {:?} serializes into byte array {:?}", x, xs);
    let xd: Vec<u8> = bincode::deserialize(&xs).unwrap();
    assert_eq!(x, xd);

    let x: Vec<f32> = vec![3.141, 2.718, 1.618];
    let xs = bincode::serialize(&x).unwrap();
    println!("Vec<f32> {:?} serializes into byte array {:?}", x, xs);
    let xd: Vec<f32> = bincode::deserialize(&xs).unwrap();
    assert_eq!(x, xd);

    let x: (i32, &str, f32, bool) = (1, "hello", 4.5, true);
    let xs = bincode::serialize(&x).unwrap();
    println!("tuple {:?} serializes into byte array {:?}", x, xs);
    let xd: (i32, &str, f32, bool) = bincode::deserialize(&xs).unwrap();
    assert_eq!(x, xd);

    let x = ((1u8, 2u16), (3.141f32, 'a'), true);
    let xs = bincode::serialize(&x).unwrap();
    println!("nested tuple {:?} serializes into byte array {:?}", x, xs);
}

Result

i32 number 5 serializes into byte array [5, 0, 0, 0]
f32 number 3.14 serializes into byte array [195, 245, 72, 64]
Vec<u8> [1, 2, 3] serializes into byte array [3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3]
Vec<f32> [3.141, 2.718, 1.618] serializes into byte array [3, 0, 0, 0, 0, 0, 0, 0, 37, 6, 73, 64, 182, 243, 45, 64, 160, 26, 207, 63]
tuple (1, "hello", 4.5, true) serializes into byte array [1, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 104, 101, 108, 108, 111, 0, 0, 144, 64, 1]
nested tuple ((1, 2), (3.141, 'a'), true) serializes into byte array [1, 2, 0, 37, 6, 73, 64, 97, 1]

What about structs and other custom Rust data types? Well, you just need to annotate them with serde and they will automagically get serialization capabilities!

use serde::{Serialize, Deserialize};
use bincode;

#[derive(Serialize, Deserialize, Debug)]
struct Point {
  x: f32,
  y: f32
}

#[derive(Serialize, Deserialize, Debug)]
struct Line {
  points: Vec<Point>,
  valid: bool,
  length: f32,
  desc: String
}

fn main() {
    let point1: Point = Point {x:1.0, y:2.0};
    let point2: Point = Point {x:3.0, y:4.0};
    let point1s = bincode::serialize(&point1).unwrap();
    let point2s = bincode::serialize(&point2).unwrap();
    println!("struct Point serializes into byte array {:?}", point1s);
    println!("struct Point serializes into byte array {:?}", point2s);

    let length = ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt();
    let valid = if length == 0.0 { false } else { true };
    let line = Line { points: vec![point1, point2], valid: valid, length: length, desc: "a thin line".to_string() };
    let lines = bincode::serialize(&line).unwrap();
    println!("struct Line serializes into byte array {:?}", lines);

    let lined: Line = bincode::deserialize(&lines).unwrap();
    assert_eq!(lined.desc, "a thin line");
    assert_eq!(lined.points[1].x, 3.0);
}

Result

struct Point serializes into byte array [0, 0, 128, 63, 0, 0, 0, 64]
struct Point serializes into byte array [0, 0, 64, 64, 0, 0, 128, 64]
struct Line serializes into byte array [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 1, 243, 4, 53, 64, 11, 0, 0, 0, 0, 0, 0, 0, 97, 32, 116, 104, 105, 110, 32, 108, 105, 110, 101]

Serialize third party library types

Many third party Rust crates already support serialization and deserialization via the serde crate. You just need to enable the serde feature when you declare the crate as dependency.

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
num-bigint = { version = "0.2", features = ["serde"] }
num = "0.2"

Note: Since the serde feature is not enabled on third party crates in the Rust Playground by default, the code examples in this article are not interactive. We will show the program output in the text. You can run these examples from this cargo project.

The example below shows how to serialize and deserialize a BigInt struct type from the num_bigint crate. It just works out of the box.

use num::bigint::ToBigInt;
use num_bigint::BigInt;

fn main() {
    let a = 3.to_bigint().unwrap();
    let x = num::pow(a, 247);
    let xs = serde_json::to_string(&x).unwrap();
    let xd: BigInt = serde_json::from_str(&xs).unwrap();
    assert_eq!(x, xd);

    println!("3**247 is {} and serializes to {}", x, xs);
}

Result

3**247 is 7062361041362837614435796717454722507454089864783271756927542774477268334591598635421519542453366332460075473278915787 and serializes to [1,[3323516107,3672165520,4080039719,3245710364,216105283,4292129601,4006727268,340573034,2851604588,3366124224,3797961372,1024846073,179]]

What about third party crates that are not yet to support the serde feature? The serde crate provides another approach called remote derive. Basically, you create a local copy of the remote type and then serialize the local type through a bridge. Check out the documentation.

Random numbers

A lot of applications require random numbers. The rand crate is a very popular library in Rust to generate random numbers. It supports a wide variety of random number generators and distributions, each with a different performance and security trade off. For more details, you can read the Rust Rand Book. To use the rand crate, just do the following in your Cargo.toml file.

[dependencies]
rand = "0.7.3"

Get a random number

To get a random number, you can simply do the following.

extern crate rand;

fn main() {
    let i: i32 = rand::random();
    println!("The random i32 is {}", i);
}

The random() is smart enough to know the primitive type it is supposed to generate. Check out the example below.

extern crate rand;

fn main() {
    let x: u8 = rand::random();
    println!("The random u8 is {}", x);

    let x: f64 = rand::random();
    println!("The random f64 is {}", x);

    let x:bool = rand::random();
    println!("The random bool {}", x);
}

What about generating a random number within a range? For that, you need to create a random number generator and call its gen_range() function.

extern crate rand;

use rand::thread_rng;
use rand::Rng;

fn main() {
    let mut rng = thread_rng();
    let y: f64 = rng.gen_range(-10.0, 10.0);
    println!("Number from -10. to 10.: {}", y);
    println!("Number from 0 to 9: {}", rng.gen_range(0, 10));
}

Get a series of random numbers

In order to get a series of random numbers, you could call the random() function multiple times. But that is slow since every time it needs to instantiate and seed a new random number generator. It is faster to create the generator once and call its gen() function repeatedly.

extern crate rand;

use rand::thread_rng;
use rand::Rng;

fn main() {
    let mut rng = thread_rng();
    for i in 1..10 {
        println!("Random number #{}: {}", i, rng.gen_range(0, 100));
    }
}

The generator can quickly fill an array with random integers.

extern crate rand;

use rand::thread_rng;
use rand::Rng;

fn main() {
    let mut arr = [0i32; 9];
    thread_rng().try_fill(&mut arr[..]);
    println!("Random number array {:?}", arr);
}

Another neat feature of the generator is that it can generate random numbers from a probability distribution.

extern crate rand;

use rand::thread_rng;
use rand::Rng;

fn main() {
    let mut rng = thread_rng();
    let distr = rand::distributions::Uniform::new_inclusive(1, 100);
    let mut nums = [0i32; 3];
    for x in &mut nums {
        *x = rng.sample(distr);
    }
    println!("Some numbers: {:?}", nums);
}

WebAssembly

Under the hood, the rand crate uses the operating system's native random functions to seed the random number generator. This initial entropy seed comes from the hardware noise. However, when Rust programs inside virtual machines like WebAssembly, it does not have access to native random hardware.

When the rand crate is compiled for WebAssembly, it has a special feature to use JavaScript's random function to seed the generator. This works when your WebAssembly application is running inside a JavaScript host, such as in a web browser or in Node.js. To enable that, do the following in your Cargo.toml.

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

Then you must use one of the wasm-bindgen compatible tools to instrument your Rust code to call external JavaScript and a JavaScript shim to be called from Rust.

Cryptography with openssl

OpenSSL is an widely used open source library to perform cryptographic operations. We can use it to encrypt, decrypt, digest, and sign data, as well as to make SSL/TLS secure connections through the Internet.

In this chapter, we will cover the openssl crate. You can read more documentation about it.

Since the openssl crate relies on the native operating system's OpenSSL library to perform cryptographic operations, it cannot be compiled into WebAssembly targets.

Let's start with an example to create a pair of RSA keys, and use them to encrypt and decrypt data.

RSA public key encryption

To use the openssl crate, you just need to add the following dependencies to your Cargo.toml file.

[dependencies]
openssl = "0.10.28"

The example below generates an RSA public and private key pair, and encrypts the keys with a phassphrase. The outputs are text strings that can be saved into files. Those files are called PEM (Privacy Enhanced Mail) files.

extern crate openssl;

use openssl::rsa::{Rsa, Padding};
use openssl::symm::Cipher;

fn main() {
    let passphrase = "rust_by_example";

    let rsa = Rsa::generate(1024).unwrap();
    let private_key: Vec<u8> = rsa.private_key_to_pem_passphrase(Cipher::aes_128_cbc(), passphrase.as_bytes()).unwrap();
    let public_key: Vec<u8> = rsa.public_key_to_pem().unwrap();

    println!("Private key: {}", String::from_utf8(private_key).unwrap());
    println!("Public key: {}", String::from_utf8(public_key).unwrap());
}

Next, we can import public and private keys from the PEM document. In the example, we demonstrate how to encrypt a byte array of data using the public key. Such encrypted data can only be decrypted by the correspding private key.

extern crate openssl;

use openssl::rsa::{Rsa, Padding};

fn main() {
    let passphrase = "rust_by_example";
    
    let public_key_pem = "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDC+Jx89MjzbWw9PPh0dffD+i2c
J7XMioLndImQvQiNJjZ00zyxjgt4+wkual+ZHhH94HIjRIeLI+ncBEjFMa1xIzHT
exz/pvJUCsHNxNK9958zR0E997xxSf3C2Lu8BWtJG348xd5QNzb+R+i963PtcAsQ
fCu+q5gbqqtQEIjlMwIDAQAB
-----END PUBLIC KEY-----";

    let private_key_pem = "-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,43371B6CECDB096AC2A362FD33BF4B07

aIs3x9UBN95VJJFsd1ddYxmwAKQdFE5BJwZVYtidV+cZ4Qpmg9tdBLm8AhF5bVGR
FzAVMxTEFQgwT4o2jH2UxRkRmChwNy6aqdGteDIK6yXQK7//GMmxhbvqMmFzwdof
2E7Jkq3BQQEqMFu2CxRUPUFYRIebEIZSDCD3PoJ6p7a77qwm/KCXCbad/DqtOGkJ
wOkPH5AXLIu02MJfs+vcLswXFMlq7aaUrAv5WGt1SpKz9Co6bplSYDG7JE+906Uw
MIg4XDJTJDKCKyDaPkMydw6StvyNuZfIYUNIofulLci7yoNEGvwQHsHCaHr6n4bt
I4iC9CbkEcPbf06HAWGFfsexeLGf9mU0HVsZi83QdMhWMbOREakFU755AMvTeB8w
IMCNn55nzJlSHooKuvJAmbqBBb4+wqgwnoYQEVZmTDZxqT/eR08Zl9d1QeKB+1fw
gjZmY/10kFLnTKlWGIaLIu60ehbXxZeFbW+m1pF9uHEiIkWgkrHNjKfzWh5EyfhY
vXxWuZH92ZP/nioGzVQr00oSEPLwW1RSoAx3jPuu1EILNu7lFL896CsDZpa1Oigf
OMxk0GhMuKs4H6TlHmx5a0TOAcGYWEbnqXi+KUw7pMPFiEs1/2crFI6QfQx8R7dL
/ohKFvksPExsB196RZ1PFyMdryOr/mCqI4nBT+KzPz4zJF2iTMGq3NFQI2MvW/4g
WMwsyQtIJQviFJpYlQpOVBFaeB69oHJMxfauM8OdEU8yomFl3sAVagNxPfiWsGt4
LRsReK2BDT/pnhhZG96qSsNPwQlrwffBleTy9BGSuHHox6A7GKyVAAOMND/TY1ak
-----END RSA PRIVATE KEY-----";

    let data = "A quick brown fox jumps over the lazy dog.";

    // Encrypt with public key
    let rsa = Rsa::public_key_from_pem(public_key_pem.as_bytes()).unwrap();
    let mut buf: Vec<u8> = vec![0; rsa.size() as usize];
    let _ = rsa.public_encrypt(data.as_bytes(), &mut buf, Padding::PKCS1).unwrap();
    println!("Encrypted: {:?}", buf);

    let data = buf;

    // Decrypt with private key
    let rsa = Rsa::private_key_from_pem_passphrase(private_key_pem.as_bytes(), passphrase.as_bytes()).unwrap();
    let mut buf: Vec<u8> = vec![0; rsa.size() as usize];
    let _ = rsa.private_decrypt(&data, &mut buf, Padding::PKCS1).unwrap();
    println!("Decrypted: {}", String::from_utf8(buf).unwrap());
}

N-dimensional array

The N-dimensional array is a widely used data structure for scientific computing and data analysis. The ndarray crate provides support for N-dimensional array in Rust. It is widely used by other crates. To use the ndarray crate, just do the following in your Cargo.toml file.

[dependencies]
ndarray = "0.13.0"

Basic operations

To create a 3-D array, and access one of its element by index, do the following. The example creates a 3x4x5 array, and we access its elements using the [[i, j, k]] notation, where i j k are index positions for the element.

extern crate ndarray;

use ndarray::Array3;

fn main() {
    let mut a3 = Array3::<f64>::zeros((3, 4, 5));
    a3[[0, 0, 0]] = 0.0;
    a3[[1, 1, 1]] = 1.0;
    a3[[2, 2, 2]] = 2.0;
    println!("The 3D array is {:?}", a3);
}

Here is another example of a 3x3 2D array.

extern crate ndarray;

use ndarray::Array2;

fn main() {
    let mut a2 = Array2::<f64>::zeros((3, 3));
    a2[[0, 0]] = 0.0;
    a2[[0, 1]] = 0.5;
    a2[[0, 2]] = -0.5;
    a2[[1, 0]] = 1.0;
    a2[[1, 1]] = 1.5;
    a2[[1, 2]] = -1.5;
    a2[[2, 0]] = 2.0;
    a2[[2, 1]] = 2.5;
    a2[[2, 2]] = -2.5;
    println!("The 2D array is {:?}", a2);
}

Manually setting array elements by index positions is tedious. Next, let's see a better way to do this.

Create arrays

The arr2 and arr3 macros allow you to create 2D and 3D arrays quickly.

The example creates a 3x3 2D array.

  • The array has 3 rows [row0, row1, row2]. For example, row0 is [1, 2, 3].
  • Each row has 3 columns col0, col1, col2. For example, row0 col0 is 1.
extern crate ndarray;

use ndarray::arr2;

fn main() {
    let mut a2 = arr2(&[[1, 2, 3],
                    [4, 5, 6],
                    [7, 8, 9]]);
    a2[[2, 1]] = 10;
    println!("The 2D array is {:?}", a2);
}

The example creates a 3x2x2 3D array.

  • The array has 3 rows [row0, row1, row2]. For example, row0 is [[1, 2], [3, 4]].
  • Each row has 2 columns [col0, col1]. For example, row0 col0 is [1, 2].
  • Each column has 2 levels lvl0, lvl1. For example, row0 col0 lvl0 is 1.
extern crate ndarray;

use ndarray::arr3;

fn main() {
    let mut a3 = arr3(&[[[1, 2], [3, 4]],
                    [[5, 6], [7, 8]],
                    [[9, 0], [1, 2]]]);
    a3[[2, 1, 1]] = 10;
    println!("The 3D array is {:?}", a3);
}

Iterate through the arrays

Using the genrows() function, you can flatten the n dimension array into an array of rows. Each row contains a simple (one dimension) array of values along the original array's shortest axis.

Using the outer_iter() function, you can deconstruct the n dimension array into a simple (one dimension) array of n-1 dimension arrays.

extern crate ndarray;

use ndarray::arr3;

fn main() {
    let a3 = arr3(&[[[1, 2], [3, 4]],
                    [[5, 6], [7, 8]],
                    [[9, 0], [1, 2]]]);
    for row in a3.genrows() {
        // Each row is a 1D array
        println!("row is {:?}", row);
    }
    for a2 in a3.outer_iter() {
        println!("2D array is {:?}", a2);
    }
}

Lazy static

The lazy_static crate is a simple but widely used macro that allows you to initialize static variables at runtime. To use it, add the following to your Cargo.toml.

[dependencies]
lazy_static = "1.4.0"

The example below shows three static variables that must be initialized by code after the program starts.

#[macro_use]
extern crate lazy_static;

use std::collections::HashMap;

lazy_static! {
    static ref HASHMAP: HashMap<u32, &'static str> = {
        let mut m = HashMap::new();
        m.insert(0, "foo");
        m.insert(1, "bar");
        m.insert(2, "baz");
        m
    };
    static ref COUNT: usize = HASHMAP.len();
    static ref NUMBER: u32 = times_two(21);
}

fn times_two(n: u32) -> u32 { n * 2 }

fn main() {
    println!("The map has {} entries.", *COUNT);
    println!("The entry for `0` is \"{}\".", HASHMAP.get(&0).unwrap());
    println!("A expensive calculation on a static results in: {}.", *NUMBER);
}

Regular expression

Coming soon!

WebAssembly

Besides operating system specific native binaries, Rust programs can also be compiled into managed code that runs inside containers or virtual machines. The reason to use managed code is runtime safety. That allows un-trusted Rust programs to run in environments such as web browsers and servers. Compared with native binaries, managed containers are also much easier to provision, limit access to resources, start, and stop on demand. A popular managed code compiler target is the WebAssembly virtual machine. With WebAssembly, your Rust programs can run side-by-side with Javascript in web browsers and servers.

In this chapter, we will first show you how to compile and run a very simple Rust function in WebAssembly. The simple example is followed by a hello world example running in web browsers.

More complex Rust / WebAssembly examples come from the server-side. We will demonstrate how to call Rust functions from Node.js Javascript applications, as well as how to access Node.js modules from Rust functions.

Compile targets for WebAssembly

There are several Rust compiler targets for WebAssembly. They differ because different WebAssembly VMs supports different extensions. For example, some WebAssembly VMs have access the WASI extension that allows access to system resources such as the file system. The wasm32 target is the most general target that produces plain WebAssembly bytecode that can run on any WebAssembly VM.

To add the wasm32 target to the compiler toolchain, do the following on your operating system's command line.

$ rustup target add wasm32-unknown-unknown
$ rustup override set nightly
$ rustup target add wasm32-unknown-unknown --toolchain nightly

Check out the complete example source code here.

Now, create a Rust library project in cargo, and create a public function like the following.

#[no_mangle]
pub extern fn triple(x: i32) -> i32 {
  return 3 * x;
}

You can build the project into WebAssembly bytecode using cargo.

$ cargo +nightly build --target wasm32-unknown-unknown --release

The compiled WebAssembly bytecode application is a single file with the wasm extension. For example, here is the wasm file from our example target/wasm32-unknown-unknown/release/my_project_name.wasm.

Run a simple WebAssembly program

In the previous article, we showed you how to compile a Rust program into a WebAssembly bytecode file. The easiest way to run a WebAssembly bytecode file is to simply load it into a web browser. Most modern web browsers already support WebAssembly.

The code example below shows how to create a WebAssembly VM, load the bytecode application, export the Rust function in the bytecode into Javascript, and then call this Rust / WebAssembly function from Javascript. All those steps could be done in the browser's Javascript console.

const response = await fetch('my_project_name.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer);
const instance = await WebAssembly.instantiate(module);
const exports = instance.exports;
const triple = exports.triple;

In the browser Javascript console, you can now call the Rust triple() function in WebAssembly and triple your input number. The code below returns number 30.

triple(10);

Of course, you can just easily load Rust compiled WebAssembly functions from your web page. Check out a full example here.

Rust Javascript binding through WebAssembly

In the previous articles, we showed how to compile a Rust function into WebAssembly, and then call this function from Javascript in a web browser. However, that approach has some severe limitations. Specifically, WebAssembly has very limited support for data types. When a WebAssembly function is exported to Javascript, the input parameters and return values are limited 32 bit integers. You cannot even pass or return string values to the WebAssembly function! You could even do a Hello World!

Fortunately, the wasm-bindgen project provides binding between Rust and Javascript. An easy way to wasm-bindgen in a browser project is to the wasm-pack tool. Here is how to install wasm-pack through the npm package manager.

$ npm install -g wasm-pack

In your project's Cargo.toml, add dependency for the wasm-bindgen crate.

[dependencies]
wasm-bindgen = "0.2.50"

Below is a Rust library function we wrote. As you can see it now takes and returns String values. The function must be annotated with #[wasm_bindgen] in order for the Rust compiler toolchain to call wasm-bindgen to generate the necessary shim code that binds Rust and Javascript through WebAssembly.

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn say(s: String) -> String {
  let r = String::from("hello ");
  return r + &s;
}

Build the wasm bytecode application and its Javascript helper files via wasm-pack. The generated files are in the pkg directory.

$ wasm-pack build --target web

From a web browser's Javascript console, you can load the generated Javascript file, export the Rust function, and call it.

import init, { say } from 'pkg/hello_lib.js';
init();
say("Michael");

The result is Hello Michael.

To see the complete source code and run it on a web page, checkout here.

Rust and WebAssembly on the server side

In the previous article, we discussed how to run Rust functions in web browsers. In this article, we will show you how to do this on the server side. We believe that Rust and WebAssembly brings a lot of benefits to server side applications.

On the server side, we use the open source Second State VM (SSVM) to execute Rust and WebAssembly functions in the Node.js environment. We still use the wasm-bindgen crate to support binding between Rust and Javascript. An easy way to wasm-bindgen in a SSVM Node.js project is to the ssvmup tool. Here is how to install ssvmup through the npm package manager.

$ npm install -g ssvmup

In your project's Cargo.toml, add dependency for the wasm-bindgen crate.

[dependencies]
wasm-bindgen = "=0.2.61"

Hello world

Below is a Rust hello world function we wrote. The function must be annotated with #[wasm_bindgen] in order for the Rust compiler toolchain to call wasm-bindgen to generate the necessary shim code that binds Rust and Javascript through WebAssembly.

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn say(s: String) -> String {
  let r = String::from("hello ");
  return r + &s;
}

Build the wasm bytecode application and its Javascript helper files via ssvmup. The generated files are in the pkg directory.

$ ssvmup build

In a Javascript application, you can load the generated Javascript file, export the Rust function, and call it.

const { say } = require('pkg/hello_lib.js');
say("Michael");

You can now run the Javascript application from Node.js command line.

$ node app.js
Hello Michael

To see the complete source code and run it in a Node.js server, checkout here.

Beyond simple arguments

Using the serde crate, we can pass in and return arbitary Javascript values to/from Rust functions. The idea is to serialize the entire set of call arguments, and return values, into JSON strings. First, add serde to your dependencies.

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wasm-bindgen = "=0.2.61"

The Rust function takes two floating point numbers, and returns the product of the two. Notice that the input and return values are all encoded in JSON.

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn area(sides: &str) -> String {
  let s: (f32, f32) = serde_json::from_str(&sides).unwrap();
  let a = s.0 * s.1;
  return serde_json::to_string(&a).unwrap();
}

Build and create the pkg folder for the compiled wasm bytecode file and the Javascript shim file my_project_name.js.

$ ssvmup build

The Javascript calling program in Node.js looks like the following.

const { area } = require('pkg/my_project_name.js');
var x = [10., 5.];
console.log( area(JSON.stringify(x)) );

Run the Node.js app shows the following result.

$ node app.js
50.0

To see the complete source code and run it in Node.js, checkout here.

Structs and objects

Using the serde crate, we can pass in complex Javascript objects and arrays to Rust functions, and return Javascript objects. Javascript objects are mapped to Rust structs, and arrays are mapped to Rust tuples.

The Rust function draw() takes two JSON strings, each representing a Point struct, and returns a JSON string representing a Line struct.

use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
  x: f32,
  y: f32
}

#[derive(Serialize, Deserialize, Debug)]
struct Line {
  points: Vec<Point>,
  valid: bool,
  length: f32,
  desc: String
}

#[wasm_bindgen]
pub fn draw(points: &str) -> String {
  let ps: (Point, Point, String) = serde_json::from_str(&points).unwrap();
  let length = ((ps.0.x - ps.1.x) * (ps.0.x - ps.1.x) + (ps.0.y - ps.1.y) * (ps.0.y - ps.1.y)).sqrt();

  let valid = if length == 0.0 { false } else { true };
  let line = Line { points: vec![ps.0, ps.1], valid: valid, length: length, desc: ps.2 };
  return serde_json::to_string(&line).unwrap();
}

Build and create the pkg folder for the compiled wasm bytecode file and the Javascript shim file my_project_name.js.

$ ssvmup build

The Javascript calling program in Node.js looks like the following.

const { draw } = require('pkg/my_project_name.js');
var x = [{x:1.5, y:3.8}, {x:2.5, y:5.8}, "A thin red line"];
console.log( draw(JSON.stringify(x)) );

Run the Node.js app shows the following result.

$ node app.js
{"points":[{"x":1.5,"y":3.8},{"x":2.5,"y":5.8}],"valid":true,"length":2.2360682,"desc":"A thin red line"}

To see the complete source code and run it in Node.js, checkout here.

Binary data

A server side function often needs to process binary data directly. The SSVM toolchain supports that use case out of the box. In this example, we will show you how to create a Rust function on the server to compute a SHA3 cryptographic digest for an arbitary input binary data array. First, add the sh3 crate as a dependency.

[dependencies]
sha3 = "0.8.2"
wasm-bindgen = "=0.2.61"

Below is the Rust function that computes the SHA3 digest value. Notice that both its input and return values are byte arrays.

use wasm_bindgen::prelude::*;
use sha3::{Digest, Sha3_256};

#[wasm_bindgen]
pub fn sha3_digest(v: &[u8]) -> Vec<u8> {
  return Sha3_256::digest(&v).as_slice().to_vec();
}

Build and create the pkg folder for the compiled wasm bytecode file and the Javascript shim file my_project_name.js.

$ ssvmup build

The Javascript calling program in Node.js looks like the following.

const { sha3_digest } = require('pkg/my_project_name.js');
console.log( sha3_digest(encoder.encode("This is an important message")) );

Run the Node.js app shows the following result.

$ node app.js
000000  57 1b e7 d1 bd 69 fb 31 9f 0a d3 fa 0f 9f 9a b5  W.çѽiû1..Óú...µ
000010  2b da 1a 8d 38 c7 19 2d 3c 0a 14 a3 36 d3 c3 cb  +Ú..8Ç.-<..£6ÓÃË

To see the complete source code and run it in Node.js, checkout here.

Call Javascript from Rust

In the previous article, we discussed how to call Rust functions from Javascript. How about the other way around? The nodejs-helper crate enables Rust functions to call Javascript functions in Node.js.

To see the complete source code and run it in Node.js, checkout here.

In your project's Cargo.toml, add dependency for the nodejs-helper and wasm-bindgen crates.

[dependencies]
wasm-bindgen = "=0.2.61"
nodejs-helper = "0.0.3"

Console and time

Recall that WebAssembly is a very simple and standalone virtual machine. It has no access the operating system's standard input / output, as well as features such as the system clock. In our Rust function, we can rely on the Javascript functions in Node.js to access those festures.

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn show_now() {
  nodejs_helper::console::log("Timestamp now: ");
  nodejs_helper::console::log(&nodejs_helper::date::timestamp());
}

#[wasm_bindgen]
pub fn utc_now() {
  nodejs_helper::console::log("UTC time: ");
  nodejs_helper::console::log(&nodejs_helper::date::utc_string());
}

#[wasm_bindgen]
pub fn my_time(tz: &str) {
  nodejs_helper::console::log(tz);
  nodejs_helper::console::log(&nodejs_helper::date::format_date("en-US", "long", "numeric", "long", "numeric", tz, "short"));
}

Build the wasm bytecode application and its Javascript helper files via ssvmup. The generated files are in the pkg directory.

$ ssvmup build

In a Javascript application, you can load the generated Javascript file, export the Rust function, and call it.

const { show_now, utc_now, my_time } = require('pkg/nodejs_example.js');

show_now();
utc_now();
my_time("America/Chicago");

You can now run the Javascript application from Node.js command line.

$ node date.js
Timestamp now:
1588013800826
UTC time:
Mon, 27 Apr 2020 18:56:40 GMT
America/Chicago
Monday, April 27, 2020, CDT

File system access

The Rust functions in this section read an image file from the local file system, resize it, and write back to the file system. It also uses the Javascript console tool to measure the time spent on each task.

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wasm-bindgen = "=0.2.61"
image = "0.23.0"

The Rust function takes a JSON string input that contains the image file names and resized dimensions.

use wasm_bindgen::prelude::*;

#[derive(Serialize, Deserialize)]
#[derive(Copy, Clone, Debug)]
pub struct Dimension {
  pub width: u32,
  pub height: u32,
}

#[derive(Serialize, Deserialize)]
pub struct Picture {
  pub dim: Dimension,
  pub raw: Vec<u8>,
}

#[wasm_bindgen]
pub fn resize_file(input: &str) {
  // Use JSON to pass multiple call arguments
  let p: (Dimension, String, String) = serde_json::from_str(input).unwrap();

  nodejs_helper::console::time("Resize file");
  let raw = nodejs_helper::fs::read_file_sync(&p.1);
  nodejs_helper::console::time_log("Resize file", "Done reading");
  let src = Picture {
    dim: p.0,
    raw: raw,
  };
  let target = resize_impl(&src);
  nodejs_helper::console::time_log("Resize file", "Done resizing");

  nodejs_helper::fs::write_file_sync(&p.2, &target.raw);
  nodejs_helper::console::time_log("Resize file", "Done writing");
  nodejs_helper::console::time_end("Resize file");
}

pub fn resize_impl(src: &Picture) -> Picture {
  // ... use the img crate to resize ...
}

Build and create the pkg folder for the compiled wasm bytecode file and the Javascript shim file my_project_name.js.

$ ssvmup build

The Javascript calling program in Node.js looks like the following.

const { resize_file } = require('../pkg/nodejs_example.js');

const dim = {
    width: 100,
    height: 100
};

resize_file(JSON.stringify([dim, 'cat.png', `test.png`]));

Run the Node.js app shows the following result.

$ node image.js
Resize file: 5.603ms Done reading
Resize file: 1506.694ms Done resizing
Resize file: 1507.634ms Done writing
Resize file: 1507.977ms

Sqlite database access

First, you must have the better-sqlite3 module installed in your Node.js setup. The Rust function will access a sqlite database through this module.

$ npm i better-sqlite3

The Rust functions to create, update, and query a Sqlite database on the local file system are as follows.

use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
pub struct User {
  pub id: u32,
  pub full_name: String,
  pub created: String,
}

#[wasm_bindgen]
pub fn create_sqlite(path: &str) {
  let sql_create = "
CREATE TABLE users (
  id INTEGER PRIMARY KEY NOT NULL,
  full_name TEXT NOT NULL,
  created DATE NOT NULL
);";
  let sql_insert = "
INSERT INTO users
VALUES
(1, 'Bob McFett', '32-01-01'),
(2, 'Angus Vader', '02-03-04'),
(3, 'Imperator Colin', '01-01-01');";

  nodejs_helper::sqlite3::create(path);
  nodejs_helper::sqlite3::update(path, sql_create);
  nodejs_helper::sqlite3::update(path, sql_insert);
}

#[wasm_bindgen]
pub fn query_sqlite(path: &str) {
  let sql_query = "SELECT * FROM users;";
  let rows: String = nodejs_helper::sqlite3::query(path, sql_query);
  let users: Vec<User> = serde_json::from_str(&rows).unwrap();
  for user in users.into_iter() {
    nodejs_helper::console::log(&(user.id.to_string() + " : " + &user.full_name));
  }
}

Build and create the pkg folder for the compiled wasm bytecode file and the Javascript shim file my_project_name.js.

$ ssvmup build

The Javascript calling program in Node.js looks like the following.

const { create_sqlite, query_sqlite } = require('pkg/nodejs_example.js');
create_sqlite("test.sqlite");
query_sqlite("test.sqlite");

Run the Node.js app shows the following result.

$ node db.js
1 : Bob McFett
2 : Angus Vader
3 : Imperator Colin

HTTP request

First, you must have the sync-request module installed in your Node.js setup. The Rust function will make synchronous HTTP requests through this module.

$ npm i sync-request

The Rust functions to access web services via HTTP/HTTPS and then save content on the local file system are as follows.

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn fetch(url: &str) {
  let content = nodejs_helper::request::fetch_as_string(url);
  nodejs_helper::console::log(url);
  nodejs_helper::console::log(&content);
}

#[wasm_bindgen]
pub fn download(url: &str, path: &str) {
  let content = nodejs_helper::request::fetch(url);
  nodejs_helper::fs::write_file_sync(path, &content);
}

Build and create the pkg folder for the compiled wasm bytecode file and the Javascript shim file my_project_name.js.

$ ssvmup build

The Javascript calling program in Node.js looks like the following.

const { fetch, download } = require('../pkg/nodejs_example.js');

fetch("https://raw.githubusercontent.com/second-state/nodejs-helper/master/LICENSE");
download("https://www.secondstate.io/", "test.html");

Run the Node.js app shows the following result.

$ node http.js
https://raw.githubusercontent.com/second-state/nodejs-helper/master/LICENSE
MIT License

Copyright (c) 2020 Second State

Permission is hereby granted, free of charge, to any person obtaining a copy
... ...