|
|
|
# Rust
|
|
|
|
|
|
|
|
The [Rust Programming Language](https://www.rust-lang.org) supports WebAssembly
|
|
|
|
as a compilation target. If you're not familiar with Rust it's recommended to
|
|
|
|
start [with its introductory documentation](https://www.rust-lang.org/learn).
|
|
|
|
Compiling to WebAssembly will involve specifying the desired target via the
|
|
|
|
`--target` flag, and to do this there are a number of "traget triples" for
|
|
|
|
WebAssembly compilation in Rust:
|
|
|
|
|
|
|
|
* `wasm32-wasi` - when using `wasmtime` this is likely what you'll be using. The
|
|
|
|
WASI target is integrated into the standard library and is intended on
|
|
|
|
producing standalone binaries.
|
|
|
|
* `wasm32-unknown-unknown` - this target, like the WASI one, is focused on
|
|
|
|
producing single `*.wasm` binaries. The standard library, however, is largely
|
|
|
|
stubbed out since the "unknown" part of the target means libstd can't assume
|
|
|
|
anything. This means that while binaries will likely work in `wasmtime`,
|
|
|
|
common conveniences like `println!` or `panic!` won't work.
|
|
|
|
* `wasm32-unknown-emscripten` - this target is intended to work in a web browser
|
|
|
|
and produces a `*.wasm` file coupled with a `*.js` file, and it is not
|
|
|
|
compatible with `wasmtime`.
|
|
|
|
|
|
|
|
For the rest of this documentation we'll assume that you're using the
|
|
|
|
`wasm32-wasi` target for compiling Rust code and executing inside of `wasmtime`.
|
|
|
|
|
|
|
|
## Hello, World!
|
|
|
|
|
|
|
|
Cross-compiling to WebAssembly involves a number of knobs that need
|
|
|
|
configuration, but you can often gloss over these internal details by using
|
|
|
|
build tooling intended for the WASI target. For example we can start out writing
|
|
|
|
a WebAssembly binary with [`cargo
|
|
|
|
wasi`](https://github.com/alexcrichton/cargo-wasi).
|
|
|
|
|
|
|
|
First up we'll [install `cargo
|
|
|
|
wasi`](https://alexcrichton.github.io/cargo-wasi/install.html):
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ cargo install cargo-wasi
|
|
|
|
```
|
|
|
|
|
|
|
|
Next we'll make a new Cargo project:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ cargo new hello-world
|
|
|
|
$ cd hello-world
|
|
|
|
```
|
|
|
|
|
|
|
|
Inside of `src/main.rs` you'll see the canonical Rust "Hello, World!" using
|
|
|
|
`println!`. We'll be executing this for the `wasm32-wasi` target, so you'll want
|
|
|
|
to make sure you're previously [built `wasmtime` and inserted it into
|
|
|
|
`PATH`](./cli-install.md);
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ cargo wasi run
|
|
|
|
info: downloading component 'rust-std' for 'wasm32-wasi'
|
|
|
|
info: installing component 'rust-std' for 'wasm32-wasi'
|
|
|
|
Compiling hello-world v0.1.0 (/hello-world)
|
|
|
|
Finished dev [unoptimized + debuginfo] target(s) in 0.16s
|
|
|
|
Running `/.cargo/bin/cargo-wasi target/wasm32-wasi/debug/hello-world.wasm`
|
|
|
|
Running `target/wasm32-wasi/debug/hello-world.wasm`
|
|
|
|
Hello, world!
|
|
|
|
```
|
|
|
|
|
|
|
|
And we're already running our first WebAssembly code inside of `wasmtime`!
|
|
|
|
|
|
|
|
While it's automatically happening for you as part of `cargo wasi`, you can also
|
|
|
|
run `wasmtime` yourself:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ wasmtime target/wasm32-wasi/debug/hello-world.wasm
|
|
|
|
Hello, world!
|
|
|
|
```
|
|
|
|
|
|
|
|
You can check out the [introductory documentation of
|
|
|
|
`cargo-wasi`](https://alexcrichton.github.io/cargo-wasi/hello-world.html) as
|
|
|
|
well for some more information.
|
|
|
|
|
|
|
|
## Writing Libraries
|
|
|
|
|
|
|
|
Previously for "Hello, World!" we created a *binary* project which used
|
|
|
|
`src/main.rs`. Not all `*.wasm` binaries are intended to be executed like
|
|
|
|
commands, though. Some are intended to be loaded into applications and called
|
|
|
|
through various APIs, acting more like libraries. For this use case you'll want
|
|
|
|
to add this to `Cargo.toml`:
|
|
|
|
|
|
|
|
```toml
|
|
|
|
# in Cargo.toml ...
|
|
|
|
|
|
|
|
[lib]
|
|
|
|
crate-type = ['cdylib']
|
|
|
|
```
|
|
|
|
|
|
|
|
and afterwards you'll want to write your code in `src/lib.rs` like so:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
#[no_mangle]
|
|
|
|
pub extern "C" fn print_hello() {
|
|
|
|
println!("Hello, world!");
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
When you execute `cargo wasi build` that'll generate a `*.wasm` file which has
|
|
|
|
one exported function, `print_hello`. We can then run it via the CLI like so:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ cargo wasi build
|
|
|
|
Compiling hello-world v0.1.0 (/home/alex/code/hello-world)
|
|
|
|
Finished dev [unoptimized + debuginfo] target(s) in 0.08s
|
|
|
|
$ wasmtime --invoke print_hello target/wasm32-wasi/debug/hello_world.wasm
|
|
|
|
Hello, world!
|
|
|
|
```
|
|
|
|
|
|
|
|
As a library crate one of your primary consumers may be other languages as well.
|
|
|
|
You'll want to consult the [section of this book for using `wasmtime` from
|
|
|
|
Python`](./lang-python.md) and after running through the basics there you can
|
|
|
|
execute our file in Python:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ cp target/wasm32-wasi/debug/hello_world.wasm .
|
|
|
|
$ python3
|
|
|
|
>>> import wasmtime
|
|
|
|
>>> import hello_world
|
|
|
|
>>> hello_world.print_hello()
|
|
|
|
Hello, world!
|
|
|
|
()
|
|
|
|
>>>
|
|
|
|
```
|
|
|
|
|
|
|
|
Note that this form of using `#[no_mangle]` Rust functions is pretty primitive.
|
|
|
|
You're only able to work with primitive datatypes like integers and floats.
|
|
|
|
While this works for some applications if you need to work with richer types
|
|
|
|
like strings or structs, then you'll want to use the support in `wasmtime` for
|
|
|
|
interface types.
|
|
|
|
|
|
|
|
## WebAssembly Interface Types
|
|
|
|
|
|
|
|
Working with WebAssembly modules at the bare-bones level means that you're only
|
|
|
|
dealing with integers and floats. Many APIs, however, want to work with things
|
|
|
|
like byte arrays, strings, structures, etc. To facilitate these interactions the
|
|
|
|
[WebAssembly Interface Types
|
|
|
|
Proposal](https://github.com/webassembly/interface-types) comes into play. The
|
|
|
|
`wasmtime` runtime has support for interface types, and the Rust toolchain has
|
|
|
|
library support in a crate called
|
|
|
|
[`wasm-bindgen`](https://crates.io/crates/wasm-bindgen).
|
|
|
|
|
|
|
|
> **Note**: WebAssembly Interface Types is still a WebAssembly proposal and is
|
|
|
|
> under active development. The toolchain may not match the exact specification,
|
|
|
|
> and during development you'll generally need to make sure tool versions are
|
|
|
|
> all kept up to date to ensure everything aligns right. This'll all smooth over
|
|
|
|
> as the proposal stabilizes!
|
|
|
|
|
|
|
|
To get started with WebAssembly interface types let's write a library
|
|
|
|
module which will generate a greeting for us. The module itself won't do any
|
|
|
|
printing, we'll simply be working with some strings.
|
|
|
|
|
|
|
|
To get starts let's add this to our `Cargo.toml`:
|
|
|
|
|
|
|
|
```toml
|
|
|
|
[lib]
|
|
|
|
crate-type = ['cdylib']
|
|
|
|
|
|
|
|
[dependencies]
|
|
|
|
wasm-bindgen = "0.2.54"
|
|
|
|
```
|
|
|
|
|
|
|
|
Using this crate, we can then update our `src/lib.rs` with the following:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
|
|
|
|
#[wasm_bindgen]
|
|
|
|
pub fn greet(name: &str) -> String {
|
|
|
|
format!("Hello, {}!", name)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Then we can build this with:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ cargo wasi build --release
|
|
|
|
Updating crates.io index
|
|
|
|
...
|
|
|
|
Finished dev [unoptimized + debuginfo] target(s) in 9.57s
|
|
|
|
Downloading precompiled wasm-bindgen v0.2.54
|
|
|
|
```
|
|
|
|
|
|
|
|
and we have our new wasm binary!
|
|
|
|
|
|
|
|
> **Note**: for now when using `wasm-bindgen` you must use `--release` mode to
|
|
|
|
> build wasi binaries with interface types.
|
|
|
|
|
|
|
|
We can then test out support for this with the CLI:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ wasmtime --invoke greet ./target/wasm32-wasi/release/hello_world.wasm "wasmtime CLI"
|
|
|
|
warning: using `--invoke` with a function that takes arguments is experimental and may break in the future
|
|
|
|
warning: using `--invoke` with a function that returns values is experimental and may break in the future
|
|
|
|
Hello, wasmtime CLI!
|
|
|
|
```
|
|
|
|
|
|
|
|
Here we can see some experimental warnings, but we got our error message printed
|
|
|
|
out! The first CLI parameter, `"wasmtime CLI"`, was passed as the first argument
|
|
|
|
of the `greet` function. The resulting string was then printed out to the
|
|
|
|
console.
|
|
|
|
|
|
|
|
Like before, we can also execute this with Python:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ cp target/wasm32-wasi/release/hello_world.wasm .
|
|
|
|
$ python3
|
|
|
|
>>> import wasmtime
|
|
|
|
>>> import hello_world
|
|
|
|
>>> hello_world.greet('python interpreter')
|
|
|
|
'Hello, python interpreter!'
|
|
|
|
>>>
|
|
|
|
```
|
|
|
|
|
|
|
|
Note that `wasm-bindgen` was originally developed for JS and usage in a browser,
|
|
|
|
but a subset of its implementation (such as arguments which are strings) are
|
|
|
|
supported for WebAssembly interface types. You can also check out the [reference
|
|
|
|
documentation for `wasm-bindgen`](https://rustwasm.github.io/wasm-bindgen/) for
|
|
|
|
more information about how it works. Note that the `wasm-bindgen` support for
|
|
|
|
wasm interface type is still in its nascent phase and is likely to be greatly
|
|
|
|
improved in the future.
|
|
|
|
|
|
|
|
## Exporting Rust functionality
|
|
|
|
|
|
|
|
Currently only Rust functions can be exported from a wasm module. Rust functions
|
|
|
|
must be `#[no_mangle]` to show up in the final binary, but if you're using
|
|
|
|
`#[wasm_bindgen]` that will happen automatically for you.
|
|
|
|
|
|
|
|
Memory is by default exported from Rust modules under the name `memory`. This
|
|
|
|
can be tweaked with the `-Clink-arg` flag to rustc to pass flags to LLD, the
|
|
|
|
WebAssembly code linker.
|
|
|
|
|
|
|
|
Tables cannot be imported at this time. When using `rustc` directly there is no
|
|
|
|
support for `anyref` and only one function table is supported. When using
|
|
|
|
`wasm-bindgen` it may inject an `anyref` table if necessary, but this table is
|
|
|
|
an internal detail and is not exported. The function table can be exported by
|
|
|
|
passing the `--export-table` argument to LLD (via `-C link-arg`) or can be
|
|
|
|
imported with the `--import-table`.
|
|
|
|
|
|
|
|
Rust currently does not have support for exporting or importing custom `global`
|
|
|
|
values.
|
|
|
|
|
|
|
|
## Importing host functionality
|
|
|
|
|
|
|
|
Only functions can be imported in Rust at this time, and they can be imported
|
|
|
|
via raw interfaces like:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
#[link(wasm_import_module = "the-wasm-import-module")]
|
|
|
|
extern "C" {
|
|
|
|
// imports the name `foo` from `the-wasm-import-module`
|
|
|
|
fn foo();
|
|
|
|
|
|
|
|
// functions can have integer/float arguments/return values
|
|
|
|
fn translate(a: i32) -> f32;
|
|
|
|
|
|
|
|
// Note that the ABI of Rust and wasm is somewhat in flux, so while this
|
|
|
|
// works, it's recommended to rely on raw integer/float values where
|
|
|
|
// possible.
|
|
|
|
fn translate_fancy(my_struct: MyStruct) -> u32;
|
|
|
|
|
|
|
|
// you can also explicitly specify the name to import, this imports `bar`
|
|
|
|
// instead of `baz` from `the-wasm-import-module`.
|
|
|
|
#[link_name = "bar"]
|
|
|
|
fn baz();
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
When you're using `wasm-bindgen` you would instead use:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
|
|
|
|
#[wasm_bindgen(module = "the-wasm-import-module")]
|
|
|
|
extern "C" {
|
|
|
|
fn foo();
|
|
|
|
fn baz();
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Note that unless you're using interface types you likely don't need
|
|
|
|
`wasm-bindgen`.
|