src/lib.rs

lib.rs is the template's main source file. The name lib.rs commonly implies that this Rust project will be compiled as a library.

It contains three key parts:

  1. #[wasm_bindgen] functions
  2. Crate imports
  3. wee_alloc optional dependecy

We'll start with the most important part of lib.rs -- the two #[wasm_bindgen] functions. In many cases, this is the only part of lib.rs you will need to modify.

1. #[wasm_bindgen] functions

The #[wasm_bindgen] attribute indicates that the function below it will be accessible both in JavaScript and Rust.


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
extern {
    fn alert(s: &str);
}
#}

The extern block imports the external JavaScript function alert into Rust. This declaration is required to call alert from Rust. By declaring it in this way, wasm-bindgen will create JavaScript stubs for alert which allow us to pass strings back and forth between Rust and JavaScript.

We can see that the alert function requires a single parameter s of type &str, a string. In Rust, any string literal such as "Hello, test-wasm!" is of type &str. So, alert could be called by writing alert("Hello, test-wasm!");.

We knew to declare alert in this way because it is how we would call alert in JavaScript -- by passing it a string argument.


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
pub fn greet() {
    alert("Hello, test-wasm!");
}
#}

If we were to write the greet function without the #[wasm_bindgen] attribute, then greet would not be easily accessible within JavaScript. Furthermore, we wouldn't be able to natively convert certain types such as &str between JavaScript and Rust. So, both the #[wasm_bindgen] attribute and the prior import of alert allow greet to be called from JavaScript.

This is all you need to know to interface with JavaScript! If you are curious about the rest, read on.

2. Crate imports


# #![allow(unused_variables)]
#fn main() {
extern crate cfg_if;
extern crate wasm_bindgen;
#}

In Cargo.toml, we included the crates cfg_if and wasm_bindgen as project dependencies.

Here, we explicitly declare that these crates will be used in lib.rs.


# #![allow(unused_variables)]
#fn main() {
mod utils;
#}

This statement declares a new module named utils that is defined by the contents of utils.rs. Equivalently, we could place the contents of utils.rs inside the utils declaration, replacing the line with:


# #![allow(unused_variables)]
#fn main() {
mod utils {
    // contents of utils.rs
}
#}

Either way, the contents of utils.rs define a single public function set_panic_hook. Because we are placing it inside the utils module, we will be able to call the function directly by writing utils::set_panic_hook(). We will discuss how and why to use this function in src/utils.rs.


# #![allow(unused_variables)]
#fn main() {
use cfg_if::cfg_if;
#}

use allows us to conveniently refer to parts of a crate or module. For example, suppose the crate cfg_if contains a function func. It is always possible to call this function directly by writing cfg_if::func(). However, this is often tedious to write. If we first specify use cfg_if::func;, then func can be called by just writing func() instead.

With this in mind, this use allows us to call the macro cfg_if! inside the crate cfg_if without writing cfg_if::cfg_if!.


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;
#}

Many modules contain a prelude, a list of things that should be automatically imported. This allows common features of the module to be conveniently accessed without a lengthy prefix. For example, in this file we can use #[wasm_bindgen] only because it is brought into scope by the prelude.

The asterisk at the end of this use indicates that everything inside the module wasm_bindgen::prelude (i.e. the module prelude inside the crate wasm_bindgen) can be referred to without prefixing it with wasm_bindgen::prelude.

For example, #[wasm_bindgen] could also be written as #[wasm_bindgen::prelude::wasm_bindgen], although this is not recommended.

3. wee_alloc optional dependecy


# #![allow(unused_variables)]
#fn main() {
cfg_if! {
    if #[cfg(feature = "wee_alloc")] {
        extern crate wee_alloc;
        #[global_allocator]
        static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
    }
}
#}

This code block is intended to initialize wee_alloc as the global memory allocator, but only if the wee_alloc feature is enabled in Cargo.toml.

We immediately notice that cfg_if! is a macro because it ends in !, similarly to other Rust macros such as println! and vec!. A macro is directly replaced by other code during compile time.

During compile time, cfg_if! evaluates the if statement. This tests whether the feature wee_alloc is present in the [features] section of Cargo.toml (among other possible ways to set it).

As we saw earlier, the default vector in [features] only contains "console_error_panic_hook" and not "wee_alloc". So, in this case, the cfg_if! block will be replaced by no code at all, and hence the default memory allocator will be used instead of wee_alloc.


# #![allow(unused_variables)]
#fn main() {
extern crate wee_alloc;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#}

However, suppose "wee_alloc" is appended to the default vector in Cargo.toml. Then, the cfg_if! block is instead replaced with the contents of the if block, shown above.

This code sets the wee_alloc allocator to be used as the global memory allocator.

What is wee_alloc?

Reducing the size of compiled WebAssembly code is important, since it is often transmitted over the Internet or placed on embedded devices.

wee_alloc is a tiny allocator designed for WebAssembly that has a (pre-compression) code-size footprint of only a single kilobyte.

An analysis suggests that over half of the bare minimum WebAssembly memory footprint is required by Rust's default memory allocator. Yet, WebAssembly code often does not require a sophisticated allocator, since it often just requests a couple of large initial allocations.

wee_alloc trades off size for speed. Although it has a tiny code-size footprint, it is relatively slow if additional allocations are needed.

For more details, see the wee_alloc repository.