Compiling from Rust to WebAssembly

이 번역은 완료되지 않았습니다. 이 문서를 번역해 주세요.

Rust 코드를 작성했다면 WebAssembly로 컴파일 할 수 있습니다! 이 튜토리얼은 Rust 프로젝트를 컴파일하여 기존 웹 애플리케이션에서 사용하기 위해 알아야 할 모든 것을 설명합니다.

Rust and WebAssembly use cases

Rust와 WebAssembly를 위한 두가지 주요 use-case가 있습니다.

  • 어플리케이션 전체를 만드는것 - Rust기반 Web app 만들기
  • 어플리케이션의 일부를 만드는것 - Rust를 기존에 존재하는 JavaScript frontend에서 사용하는것

당분간, Rust 팀은 후자의 경우에 초점을 맞출 것입니다.그래서 여기서는 두번째 내용에 대해 다루겠습니다. 첫번째 use-case는 yew와 같은 프로젝트를 한번 확인해보세요.

이 튜토리얼에서는 Rust에서 npm 패키지를 빌드하는 도구인 wasm-pack을 사용하여 npm 패키지를 빌드합니다. 이 패키지에는 WebAssembly 및 JavaScript 코드만 포함되므로 패키지 사용자는 Rust를 설치할 필요가 없습니다. 심지어 WebAssembly에서 작성된 것임을 알지 못할 수도 있습니다!

Rust Environment Setup

환경을 설정하기 위해 필요한 모든 단계를 수행해 봅시다.

Install Rust

Install Rust by going to the Install Rust page and following the instructions. This will install a tool called "rustup", which lets you manage multiple versions of Rust. By default, it installs the latest stable Rust release, which you'll want to use for general Rust development. Rustup installs rustc, the Rust compiler, as well as cargo, Rust's package manager, rust-std, Rust's standard libraries, and some helpful docs — rust-docs.

Note: Pay attention to the post-install note about needing to have cargo's bin directory in your system PATH. This should be added automatically, but you'll need to restart your terminal for it to take effect.

The tools used in this tutorial require at least Rust 1.30, so for the moment you'll need these additional steps (at the time of this writing, 1.30 is in beta).

Install the Rust Beta:

$ rustup install beta

Set Rust to use the beta version as the default:

$ rustup default beta-x86_64-apple-darwin

Note: To find out what the beta package is called on your system, you can use rustup show.

Note: On October 25, 2018, Rust 1.30 will be released, and so these additional steps will no longer be needed.

wasm-pack

To build our package, we'll need an additional tool, wasm-pack. This will help us compile our code to WebAssembly, as well as produce the right packaging for npm. To download and install it, enter the following command into your terminal:

$ cargo +beta install wasm-pack

Install Node.js and get an npm account

We'll be building an npm package in this tutorial, and so you'll need to have Node.js and npm installed. Additionally, we'll be publishing our package to npm, and so you'll need an npm account as well. They're free! You don't technically have to publish the package, but using it is much easier that way, and so we'll be assuming that you do in this tutorial.

To get Node.js and npm, go to the Get npm! page and follow the instructions. When it comes to picking a version, choose any one you'd like; this tutorial isn't version-specific.

To get an npm account, go to the npm signup page and fill out the form.

Next, at the command line run npm adduser:

> npm adduser
Username: yournpmusername
Password:
Email: (this IS public) you@exmaple.com

You'll need to fill out your own username, password, and email. If it worked, you'll see this printed:

Logged in as yournpmusername on https://registry.npmjs.org/.

If something didn't work, contact npm to get it sorted.

Building our WebAssembly npm package

Enough setup, let's create a new package in Rust. Go to wherever you keep your personal projects, and do this:

$ cargo new --lib hello-wasm
     Created library `hello-wasm` project

This will create a new library in a subdirectory named hello-wasm with everything you need to get going:

+-- Cargo.toml
+-- src
    +-- lib.rs

First up, we have Cargo.toml; this is the way that we can configure our build. If you've used Gemfile from Bundler or package.json from npm , you'll feel right at home; Cargo works in a similar manner to both of them.

Next, Cargo has generated some Rust code for us in src/lib.rs:

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

We won't use this test code at all, so go ahead and delete it.

Let's write some Rust!

Let's put this code into src/lib.rs instead:

extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

This is the contents of our Rust project. It has three main parts, let's talk about them in turn. We'll give a high-level explanation here, and gloss over some details; to learn more about Rust, please check the free online book The Rust Programming Language.

Using wasm-bindgen to communicate between Rust and JavaScript

The first part looks like this:

extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

The first line says "hey Rust, we're using a library called wasm_bindgen." Libraries are called "crates" in Rust, and we're using an external one, so "extern".

Get it? Cargo ships crates.

The third line contains a use command, which imports code from a library into your code. In this case, we're importing everything in the wasm_bindgen::prelude module. We'll use these features in the next section.

Before we move on to the next section, we should talk a bit more about wasm-bindgen.

wasm-pack uses wasm-bindgen, another tool, to provide a bridge between the types of JavaScript and Rust. It allows JavaScript to call a Rust API with a string, or a Rust function to catch a JavaScript exception.

We'll be using wasm-bindgen's functionality in our package. In fact, that's the next section!

Calling external functions in JavaScript from Rust

The next part looks like this:

#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}

The bit inside the #[] is called an "attribute", and it modifies the next statement somehow. In this case, that statement is an extern, which tells Rust that we want to call some externally defined functions. The attribute says "wasm-bindgen knows how to find these functions".

The third line is a function signature, written in Rust. It says "the alert function takes one argument, a string named s."

You may have a suspicion as to what this function is, and your suspicion may be right: this is the alert function provided by JavaScript! We'll be calling this function in the next section.

Whenever you want to call new JavaScript functions, you can write them in here, and wasm-bindgen will take care of setting everything up for you. Not everything is supported yet, but we're working on it! Please file bugs if something is missing.

Producing Rust functions that JavaScript can call

The final part is this one:

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

Once again, we see the #[wasm_bindgen] attribute. In this case, it's not modifying an extern block, but a fn; this means that we want this Rust function to be able to be called by JavaScript. It's the opposite of extern: these aren't the functions we need, but the functions we're giving out to the world!

This function is named greet, and takes one argument, a string (written &str), name. It then calls the alert function we asked for in the extern block above. It passes a call to the format! macro, which lets us concatenate strings.

format! takes two arguments in this case, a format string, and a variable to put in it. The format string is the "Hello, {}!" bit. It contains {}s, where variables will be interpolated. The variable we're passing is name, the argument to the function, so if we call greet("Steve") we should see "Hello, Steve!".

This is passed to alert(), so when we call this function we should see an alert box with "Hello, Steve!" in it!

Now that our library is written, let's build it.

Compiling our code to WebAssembly

To compile our code correctly, we first need to configure it with Cargo.toml. Open this file up, and change it's contents to look like this:

[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
repository = "https://github.com/yourgithubusername/hello-wasm"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

You'll need to fill in your own repository, and Cargo should have filled in authors based on the info git uses.

The big part to add is the stuff at the bottom. The first part — [lib] — tells Rust to build a cdylib version of our package; we won't get into what that means in this tutorial. For more, consult the Cargo and Rust Linkage documentation.

The second section is the [dependencies] section. Here's where we tell Cargo what version of wasm-bindgen we want to depend on; in this case, that's any 0.2.z version (but not 0.3.0 or above).

Building the package

Now that we've got everything set up, let's build! Type this into your terminal:

$ wasm-pack build --scope mynpmusername

This will do a number of things (and they take a lot of time, especially the first time you run wasm-pack). To learn about them in detail, check out this blog post on Mozilla Hacks. In short, wasm-pack build will:

  1. Compile your Rust code to WebAssembly.
  2. Run wasm-bindgen on that WebAssembly, generating a JavaScript file that wraps up that WebAssembly file into a module npm can understand.
  3. Create a pkg directory and move that JavaScript file and your WebAssembly code into it.
  4. Read your Cargo.toml and produce an equivalent package.json.
  5. Copy your README.md (if you have one) into the package.

The end result? You have an npm package inside of the pkg directory.

A digression about code size

If you check out the generated WebAssembly code size, it may be a few hundred kilobytes. We haven't instructed Rust to optimize for size at all, and doing so cuts down on the size a lot. This is off-topic for this tutorial, but if you'd like to learn more, check out the Rust WebAssembly Working Group's documentation on Shrinking .wasm Size.

Publishing our package to npm

Let's publish our new package to the npm registry:

$ cd pkg
$ npm publish --access=public

We now have an npm package, written in Rust, but compiled to WebAssembly. It's ready for use from JavaScript, and doesn't require the user to have Rust installed; the code included was the WebAssembly code, not the Rust source!

Using our package on the web

Let's build a website that uses our new package! Many people use npm packages through various bundler tools, and we'll be using one of them, webpack, in this tutorial. It's only slightly more complex, and shows off a more realistic use-case.

Let's move back out of the pkg directory, and make a new directory, site, to try this out in:

$ cd ../..
$ mkdir site
$ cd site

Create a new file, package.json, and put the following code in it:

{
  "scripts": {
    "serve": "webpack-dev-server"
  },
  "dependencies": {
    "@mynpmusername/hello-wasm": "^0.1.0"
  },
  "devDependencies": {
    "webpack": "^4.0.1",
    "webpack-cli": "^2.0.10",
    "webpack-dev-server": "^3.1.0"
  }
}

Note that you'll need to fill in your own username, after the @, in the dependencies section.

Next, we need to configure Webpack. Create webpack.config.js and put the following in it:

const path = require('path');
module.exports = {
  entry: "./index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "index.js",
  },
  mode: "development"
};

Now we need an HTML file; create an index.html and give it the following contents:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>hello-wasm example</title>
  </head>
  <body>
    <script src="./index.js"></script>
  </body>
</html>

Finally, create the index.js referenced in the HTML file and give it these contents:

const js = import("./node_modules/@yournpmusername/hello_wasm/hello_wasm.js");
js.then(js => {
  js.greet("WebAssembly");
});

Note that you need to fill in your npm username again.

This imports our module from the node_modules folder. This isn't considered a best practice, but this is a demo, so we'll work with it for now. Once it's loaded, it calls the greet function from that module, passing "WebAssembly" as a string. Note how there's nothing special here, yet we're calling into Rust code! As far as the JavaScript code can tell, this is just a normal module.

We're done making files! Let's give this a shot:

$ npm install
$ npm run serve

This will start up a web server. Load up http://localhost:8080 and you should see an alert box come on the screen, with Hello, WebAssembly! in it! We've successfully called from JavaScript into Rust, and from Rust into JavaScript.

Conclusion

This is the end of our tutorial; we hope you've found it useful.

There's lots of exciting work going on in this space; if you'd like to help make it even better, check out the Rust Webassembly Working Group.

문서 태그 및 공헌자

이 페이지의 공헌자: limkukhyun
최종 변경자: limkukhyun,