Skip to content

Commit

Permalink
Merge pull request #490 from danwilhelm/template_deep_dive
Browse files Browse the repository at this point in the history
Complete template deep dives
  • Loading branch information
ashleygwilliams authored Jan 13, 2019
2 parents 4cd7790 + 5205847 commit ea99a2d
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 18 deletions.
46 changes: 30 additions & 16 deletions docs/src/tutorial/template-deep-dive/cargo-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
`Cargo.toml` is the manifest file for Rust's package manager, `cargo`. This file contains
metadata such as name, version, and dependencies for packages, which are call "crates" in Rust.

There's a bunch of metadata that the template gives us, but there are 3 key parts to discuss:
There's a bunch of metadata that the template gives us, but there are three key parts to discuss:

- [`crate-type`](#a1-crate-type)
- [`wasm-bindgen` dependency](#a2-wasm-bindgen-dependency)
- [`[features]` and `wee-alloc`, `console-error-panic-hook` dependencies](#a3-features-and-wee-alloc-console-error-panic-hook-dependencies)
1. [`crate-type`](#a1-crate-type)
2. [`wasm-bindgen` dependency](#a2-wasm-bindgen-dependency)
3. [`[features]` and `wee_alloc`, `console_error_panic_hook` dependencies](#a3-features-and-wee_alloc-console_error_panic_hook-dependencies)

<hr/>

## 1. `crate-type`

```toml
[lib]
crate-type = ["cdylib"]
crate-type = ["cdylib", "rlib"]
```

A Rust-`wasm` crate is a bit different from a normal crate, and as a result, we need to note
Expand All @@ -33,6 +33,9 @@ as a dynamic library to be loaded from another language. In our case, we'll be c
`.wasm` file, but this output type will create `*.so` files on Linux, `*.dylib` files on
macOS, and `*.dll` files on Windows in non-`wasm` circumstances.

`#[crate_type = "rlib"]` signifies that an intermediate "Rust library" file will be produced.
This allows tests to use the main crate.

You can read more about linking and crate types, [here](https://doc.rust-lang.org/reference/linkage.html).

## 2. `wasm-bindgen` dependency
Expand All @@ -52,23 +55,24 @@ We'll see more about how to use this library when we discuss what has been gener
there is no `^` or `~` symbol- it looks like we're locking to the `0.2` version.
However, that's not the case! In Rust, the `^` is implied.

## 3. `[features]` and `wee-alloc`, `console-error-panic-hook` dependencies
## 3. `[features]` and `wee_alloc`, `console_error_panic_hook` dependencies

As part of our effort to design a template that helps people discover useful crates
for their particular use case, this template includes 2 dependencies that can be
for their particular use case, this template includes two dependencies that can be
very useful for folks developing Rust-`wasm` crates: `console-error-panic-hook` and
`wee-alloc`.

Because these dependencies are useful primarily in a specifc portion of the Rust-`wasm`
Because these dependencies are useful primarily in a specific portion of the Rust-`wasm`
crate development workflow, we've also set up a bit of glue code that allows us to include
them both as dependences, but allowing for them to be optionally included.
them both as dependencies, but also allows them to be optionally included.

```toml
[features]
default-features = ["console_error_panic_hook", "wee_alloc"]
default = ["console_error_panic_hook"]

[dependencies]
cfg-if = "0.1.2"
wasm-bindgen = "0.2"

# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
Expand All @@ -79,12 +83,22 @@ console_error_panic_hook = { version = "0.1.1", optional = true }
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
wee_alloc = { version = "0.4.1", optional = true }
#
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4.2", optional = true }
```

[`cfg-if`] allows us to check if certain features are enabled on a rust crate. We'll
use this crate in `utils.rs` to optionally enable `console_error_panic_hook` or
`wee_alloc`. By default, we have them enabled. To disable them, we can remove their
entry from the `default-features` vector.
[`cfg-if`] allows us to check if certain features are enabled on a Rust crate. We'll
use this crate later to optionally enable `console_error_panic_hook` or
`wee_alloc`.

By default, only `console_error_panic_hook` is enabled. To disable either
feature, we can remove its name from the `default` vector.

To learn more about these features, we discuss them in-depth in the `src/lib.rs` and
`src/utils.rs` sections.

Briefly, they include:

To learn more about these features, we discuss them in depth in the `utils.rs` section.
+ **console_error_panic_hook** for logging panic messages to the developer console.
+ **wee_alloc**, an allocator optimized for small code size.
126 changes: 125 additions & 1 deletion docs/src/tutorial/template-deep-dive/src-lib-rs.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,127 @@
# src/lib.rs

🚧 COMING SOON 🚧
`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`](#a1-wasm_bindgen-functions)
2. [Crate imports](#a2-crate-imports)
3. [`wee_alloc` optional dependecy](#a3-wee_alloc-optional-dependecy)
- [What is `wee_alloc`?](#what-is-wee_alloc)

---

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.

```rust
#[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.

```rust
#[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

```rust
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`.

```rust
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:

```rust
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`.


```rust
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!`.

```rust
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

```rust
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`.

```rust
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](http://fitzgeraldnick.com/2018/02/09/wee-alloc.html) 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](https://github.com/rustwasm/wee_alloc).
65 changes: 64 additions & 1 deletion docs/src/tutorial/template-deep-dive/src-utils-rs.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,66 @@
# src/utils.rs

🚧 COMING SOON 🚧
The purpose of `utils.rs` is to define the `utils` module, which contains a single function `set_panic_hook`. This function becomes part of the `utils` module in `lib.rs`, as described in the preceding section.

If the `console_error_panic_hook` feature is not enabled, then `set_panic_hook` is defined to be an inlined empty function. So, there is no run-time performance or code-size penalty incurred by its use.

We will discuss:
1. [Defining `set_panic_hook`](#a1-defining-set_panic_hook)
2. [What is `console_error_panic_hook`?](#a2-what-is-console_error_panic_hook)


---

## 1. Defining `set_panic_hook`

```
use cfg_if::cfg_if;
```

This allows us to write `cfg_if!` instead of `cfg_if::cfg_if!`, identically to the line in `src/lib.rs`.

```
cfg_if! {
if #[cfg(feature = "console_error_panic_hook")] {
extern crate console_error_panic_hook;
pub use self::console_error_panic_hook::set_once as set_panic_hook;
} else {
#[inline]
pub fn set_panic_hook() {}
}
}
```

As described in the preceding section, the macro `cfg_if!` evaluates the `if` statement during compile time. This is possible because it is essentially testing whether `"console_error_panic_hook"` is defined in the `[features]` section of `Cargo.toml`, which is available during compile time.

The entire macro block will either be replaced with the statements in the `if` block or with those in the `else` block. These two cases are now described in turn:

```
extern crate console_error_panic_hook;
pub use self::console_error_panic_hook::set_once as set_panic_hook;
```

Due to the `use` statement, the function `self::console_error_panic_hook::set_once` can now be accessed more conveniently as `set_panic_hook`. Due to `pub`, this function will be publicly accessible outside of the `utils` module as `utils::set_panic_hook`.

```
#[inline]
pub fn set_panic_hook() {}
```

An inline function replaces the function call with the contents of the function during compile time. Here, `set_panic_hook` is defined to be an empty inline function. This allows the use of `set_panic_hook` without any run-time or code-size performance penalty if the feature is not enabled.

## 2. What is `console_error_panic_hook`?

The crate `console_error_panic_hook` enhances error messages in the web browser. This allows you to easily debug WebAssembly code.

Let's compare error messages before and after enabling the feature:

**Before:** `"RuntimeError: Unreachable executed"`

**After:** `"panicked at 'index out of bounds: the len is 3 but the index is 4', libcore/slice/mod.rs:2046:10"`

To do this, a panic hook for WebAssembly is provided that logs panics to the developer console via the JavaScript `console.error` function.

Note that although the template sets up the function, your error messages will not automatically be enhanced. To enable the enhanced errors, call the function `utils::set_panic_hook()` in `lib.rs` when your code first runs. The function may be called multiple times if needed.

For more details, see the [`console_error_panic_hook` repository](https://github.com/rustwasm/console_error_panic_hook).

0 comments on commit ea99a2d

Please sign in to comment.