This article was first published at: blog.zhangbing.site

Rust is a programming language that originated from Mozilla Research in 2010. Today, all the big companies use it.

Amazon and Microsoft both recognize it as the best alternative to C/C ++ in their systems, but Rust is more than that. Companies like Figma and Discord are now leading the way by using Rust in their client applications.

This Rust tutorial aims to provide a brief introduction to Rust, how to use it in a browser, and when you should consider using it. I’ll start by comparing Rust and JavaScript, and then walk you through the steps of Rust running in a browser. Finally, I’ll present a quick performance evaluation of a COVID Simulator Web application using Rust and JavaScript.

In a nutshell

Rust is conceptually very different from JavaScript. But there are similarities to point out, and let’s look at both sides of the question.

The similarity

Both languages have a modern package management system. JavaScript has NPM, Rust has Cargo. Rust has Cargo. Toml instead of package.json for dependency management. To create a new project, use cargo Init, and to run it, use Cargo Run. Not too strange, is it?

There are a lot of cool features in Rust that you already know from JavaScript, but the syntax is slightly different. Using this common JavaScript pattern, apply a closure to each element in the array:

let staff = [
  { name: "George".money: 0 },
  { name: "Lea".money: 500000},];let salary = 1000;
staff.forEach((employee) = > {
  employee.money += salary;
});
Copy the code

In Rust, we can write:

let salary = 1000; staff.iter_mut().for_each( |employee| { employee.money += salary; });Copy the code

Admittedly, used to this syntax takes time, use the pipe (|) instead of brackets. But after getting over the initial awkwardness, I found it read more clearly than the other set of parentheses.

Here’s another example of object deconstruction in JavaScript:

let point = { x: 5.y: 10 };
let { x, y } = point;
Copy the code

Also in Rust:

let point = Point { x: 5, y: 10 };
let Point { x, y } = point;
Copy the code

The main difference is that in Rust we must specify a type (Point) and, more generally, Rust needs to know all types at compile time. But unlike most other compiled languages, the compiler does its best to infer the type itself.

To further explain this, here is code that works in C++ and many other languages. Each variable requires an explicit type declaration.

int a = 5;
float b = 0.5;
float c = 1.5 * a;
Copy the code

In JavaScript and Rust, this code is valid:

let a = 5;
let b = 0.5;
let c = 1.5 * a;
Copy the code

The list of sharing features is endless:

  • Rust hasasync + awaitSyntax.
  • Arrays can be like letLet array = [1, 2, 3]As simple as creating.
  • The code is organized by modules, with explicit imports and exports.
  • Strings are encoded in Unicode and handling special characters is no problem.

I could go on and on, but I think my point is clear by now. Rust has a rich set of features that are also used in modern JavaScript.

The difference between

Rust is a compiled language, which means that no runtime can execute Rust code. An application can only run after the compiler (RUSTC) has done its magic. The benefit of this approach is usually better performance.

Fortunately, Cargo solved the problem of calling the compiler for us. With Webpack, we can also hide Cargo behind the NPM Run build. With this guide, you can preserve the normal workflow of a Web developer as long as Rust is set up for your project.

Rust is a strongly typed language, which means that all types must match at compile time. For example, you cannot call a function with the wrong type of arguments or the wrong number of arguments. The compiler will catch this error for you before you encounter it at runtime. The obvious comparison is TypeScript, and if you like TypeScript, chances are you’ll like Rust.

But don’t worry: If you don’t like TypeScript, Rust may still be for you. Rust, built from scratch in recent years, takes into account everything humans have learned about programming language design over the past few decades. The result is a refreshingly concise language.

Pattern matching in Rust is one of my favorite features, and other languages have switches and cases to avoid long chains like this:

if (x == 1) {
  // ...
} else if (x == 2) {
  // ...
} else if (x == 3 || x == 4) {
  // ...
} // ...
Copy the code

Rust uses the following more elegant matches:

match x {
  1= > {/* Do something if x == 1 */},
  2= > {/* Do something if x == 2 */},
  3 | 4= > {/* Do something if x == 3 || x == 4 */},
  5.10= > {/* Do something if x >= 5 && x <= 10 */}, _ = > {/* Catch all other cases */}}Copy the code

I think this is pretty neat, and I hope JavaScript developers appreciate this syntax extension as well.

Unfortunately, we also have to talk about the dark side of Rust. To be blunt, using a strict type system can sometimes feel cumbersome. If you thought the type systems of C++ or Java were rigid, get ready for Rust to take a hard ride.

Personally, I like the Rust part. I rely on a strict type system, so I can turn off a part of my brain — a part of my brain that fires up violently whenever I find myself writing JavaScript. But I know it can be annoying for beginners to fight the compiler all the time. We’ll see some of that later in the Rust tutorial.

Hello Rust

Now, let’s run a Hello World in a browser with Rust, and first make sure all the necessary tools are installed.

tool

Install Cargo + RUSTC using RUSTup. Rustup is the recommended way to install Rust, which installs the latest stable version of Rust’s compiler (RUSTC) and package manager (Cargo). It will install the latest stable version of Rust’s compiler (RUSTC) and package manager (Cargo). It can also manage beta builds and nightly builds, but this is not required for this example.

  • Enter on the terminalcargo --versionTo check the installation, which you should be able to seeCargo 1.48.0 cbdd2dc (65 2020-10-14)Something like that.
  • Also check Rustup:rustup --versionShould produceRustup 1.23.0 (00924C9BA 2020-11-27).

Install the wasm – pack. This is to integrate the compiler with NPM.

  • By enteringwasm-pack --versionTo check the installation, which should be provided to youWasm - pack 0.9.1Something like that.

We also need Node and NPM. We have a full article explaining the best way to install both.

Writing Rust code

Now that everything is installed, let’s create the project. The final code can also be found in the GitHub repository. We start with a Rust project that can be compiled into an NPM package, followed by JavaScript code to import that package.

To create a Rust project called hello-world, use cargo init –lib hello-world. This creates a new directory and generates all the files needed for the Rust library:

├── Hello-world ├─ CargoCopy the code

The Rust code will be placed in lib.rs, before which we have to adjust Cargo. Toml. It uses TOML to define dependencies and other package information. If you want to see Hello World in your browser, add the following line count somewhere in Cargo. Toml (for example, at the end of the file).

[lib]
crate-type = ["cdylib"]
Copy the code

This tells the compiler to create a library in C-compatible mode. Obviously we didn’t use C in our example. C-compatible simply means not rust-specific, which is what we need to use the library in JavaScript.

We also need two external libraries to add as separate lines to the dependencies section.

[dependencies]
wasm-bindgen = "0.2.68"
web-sys = {version = "0.3.45", features = ["console"]}
Copy the code

These are dependencies from Crates. IO, which is the default package repository used by Cargo.

Wasm-bindgen is necessary to create an entry point that we can call later from JavaScript. (You can find the full documentation here.) The value “0.2.68” specifies the version.

Web-sys contains Rust bindings for all web apis that will give us access to the browser console. Note that we had to explicitly select the console functionality, and our final binary will contain only the Web API bindings selected as such.

Next comes the actual code inside lib.rs. Automatically generated unit tests can be deleted. Simply replace the contents of the file with the following code:

use wasm_bindgen::prelude::*;
use web_sys::console;

#[wasm_bindgen]
pub fn hello_world() {
    console::log_1("Hello world");
}
Copy the code

The use statement at the top is used to import projects from other modules. This is similar to import in JavaScript).

Pub fn hello_world () {… } declare a function. The pub modifier is short for “public” and acts like export in JavaScript. Comment # [WASm_bindgen] is specific to Rust compilation as [WebAssembly (Wasm)](webAssembly.org/ “WASm_bindgen] is specific to Rust compilation as [WebAssembly (Wasm) “Wasm_bindgen” is specific to Rust compilation as [WebAssembly (Wasm)”)”). We need it here to ensure that the compiler exposes the wrapper function to JavaScript.

In the function body, “Hello World” is printed to the console. Console :: log_1() in Rust is a wrapper around a call to console.log().

Have you noticed the _1 suffix in function calls? This is because JavaScript allows a variable number of arguments, whereas Rust does not. To solve this problem, WASm_Bindgen generates a function for each number of arguments. Yes, this is going to get ugly fast! But it works. A complete list of functions that can be called in the Rust console is provided in the Web-SYS documentation.

Now that we have everything in place, try compiling it with the following command. This will download all the dependencies and compile the project, which may take some time the first time.

cd hello-world
wasm-pack build
Copy the code

Ha! Ha! The Rust compiler is not happy with us.

error[E0308]: mismatched types
 --> src\lib.rs:6:20
  |
6 |     console::log_1("Hello world");
  |                    ^^^^^^^^^^^^^ expected struct `JsValue`, found `str`
  |
  = note: expected reference `&JsValue`
             found reference `&'static str
Copy the code

Note: If you see other errors (Error: linking with CC failed: exit code: 1) and you are using Linux, there is a lack of cross-compile dependencies. Sudo apt install gcc-multilib should resolve this problem.

As I mentioned earlier, compilers are strict. When it expects a JsValue reference as an argument to a function, it does not accept a static string. To satisfy the compiler’s requirements, explicit conversions must be performed.

console::log_1(&"Hello world".into());
Copy the code

Methods [into ()] (doc.rust-lang.org/std/convert… “Into (“into()”)”) converts one value to another. The Rust compiler is smart enough to defer which types participate in the conversion because the function signature leaves only one possibility open. In this case, it will be converted to JsValue, which is a wrapper type for a value managed by JavaScript. Then we have to add &, which is passed by reference rather than by value, or the compiler will complain again.

Try running wASM-pack Build again, and if all goes well, the last line should look like this:

[INFO]: :-) Your wasm pkg is ready to publish at /home/username/intro-to-rust/hello-world/pkg.
Copy the code

If you can make it this far, you can now compile Rust manually. Next, we’ll integrate it with NPM and WebPack, which will automatically do the work for us.

JavaScript integration

In this case, I decided to put package.json in the Hello-world directory. We can also use different directories for Rust projects and JavaScript projects, which is a matter of taste.

Here is my package.json file. The easiest way to follow is to copy it and run NPM install, or run NPM init and just copy dev dependencies:

{
  "name": "hello-world"."version": "1.0.0"."description": "Hello world app for Rust in the browser."."main": "index.js"."scripts": {
    "build": "webpack"."serve": "webpack serve"
  },
  "author": "Jakob Meier <[email protected]>"."license": "(MIT OR Apache-2.0)"."devDependencies": {
    "@wasm-tool/wasm-pack-plugin": "~ 1.3.1." "."@webpack-cli/serve": "^ 1.1.0." "."css-loader": "^ 5.0.1." "."style-loader": "^ 2.0.0." "."webpack": "~ 5.8.0"."webpack-cli": "~ 4.2.0"."webpack-dev-server": "~ 3.11.0"}}Copy the code

As you can see, we are using WebPack 5. Wasm-pack can also be used with older versions of WebPack, even without bundles. But each setting works a little differently, and I recommend that you use the exact same version as you follow this Rust tutorial.

Another important dependency is the wASM-pack-plugin. This is a Webpack plug-in designed to load Rust packages built using WASM-Pack.

Moving on, we also need to create the webpack.config.js file to configure webpack. It should look something like this:

const path = require("path");
const webpack = require("webpack");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");

module.exports = {
  entry: "./src/index.js".output: {
    path: path.resolve(__dirname, "dist"),
    filename: "index.js",},plugins: [
    new WasmPackPlugin({
      crateDirectory: path.resolve(__dirname, "."),})],devServer: {
    contentBase: "./src".hot: true,},module: {
    rules: [{test: /\.css$/i,
        use: ["style-loader"."css-loader"],},],},experiments: {
    syncWebAssembly: true,}};Copy the code

All paths are configured with Rust code and JavaScript code side by side. Index.js will be in the SRC folder, next to lib.rs. You can always adjust these if you like different Settings.

You’ll also notice that we use WebPack Experiments, which is a new option introduced in WebPack 5. Make sure syncWebAssembly is set to true.

Finally, we must create the JavaScript entry point SRC /index.js:

import(".. /pkg")
  .catch((e) = >
    console.error("Failed loading Wasm module:", e)
  )
  .then((rust) = > rust.hello_world());
Copy the code

We must load the Rust module asynchronously. Calling rust.hello_world() invokes a generated wrapper function, which in turn invokes the rust function hello_world defined in lib.rs.

Now, running NPM Run Serve should compile everything and start the development server. We have not defined an HTML file, so nothing is displayed on the page. You may also have to manually turn to http://localhost:8080/index, because http://localhost:8080 is only lists the files and does not perform any code.

After opening a blank page, open the developer console. Hello World should have a log entry.

Ok, that’s a lot of work for a simple Hello World. But now that everything is in place, we can easily extend Rust code without worrying about this. After saving the changes to lib.rs, you should automatically see recompilation and live updates in your browser, just like JavaScript.

When to use Rust

Rust is not a generic substitute for JavaScript. It only runs in a browser with Wasm, which limits its usefulness to a large extent. Even if you could replace almost all your JavaScript code with Rust, that’s a bad idea if you really want to, and it’s not the purpose of Wasm. For example, Rust is not a good place to interact with your site’s UI.

I see Rust + Wasm as an additional option that can be used to run CPU-heavy workloads more efficiently. At the expense of larger downloads, Wasm avoids the parsing and compilation overhead that JavaScript code faces. This, combined with the compiler’s robust optimization, may lead to better performance. This is often why companies choose Rust for specific projects. Another reason to choose Rust could be language preference, but that’s a completely different discussion that I won’t discuss here.