|
1 | 1 | # Components in Rust
|
2 | 2 |
|
3 |
| -Rust has first-class support for the component model via [the `cargo component` tool](https://github.com/bytecodealliance/cargo-component). |
| 3 | +Rust has first-class support for the component model via the [`cargo-component` tool][cargo-component]. |
| 4 | +We will be using the `cargo component` subcommand to create WebAssembly components using Rust as |
| 5 | +the component's implementation language. |
4 | 6 |
|
5 |
| -`cargo component` is is a `cargo` subcommand for creating WebAssembly components |
6 |
| -using Rust as the component's implementation language. |
| 7 | +> [!NOTE] |
| 8 | +> You can find more details about `cargo-component` on [crates.io](https://crates.io/crates/cargo-component). |
7 | 9 |
|
8 |
| -## 1. Installing `cargo component` |
9 |
| - |
10 |
| -To install `cargo component`, run: |
| 10 | +## 1. Setup |
11 | 11 |
|
| 12 | +Install [`cargo-component`][cargo-component-install]: |
12 | 13 | ```sh
|
13 |
| -cargo install cargo-component |
| 14 | +cargo install --locked cargo-component |
14 | 15 | ```
|
15 |
| - |
16 |
| -> You can find more details about `cargo component` in its [crates.io page](https://crates.io/crates/cargo-component). |
17 |
| -
|
18 |
| -## 2. Scaffold a Component with `cargo component` |
19 |
| - |
20 |
| -Create a Rust library that implements the `add` function in the [`adder` world][adder-wit]. |
21 |
| - |
22 |
| -First scaffold a project: |
23 |
| - |
| 16 | +Install [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools#installation): |
24 | 17 | ```sh
|
25 |
| -$ cargo component new adder --lib && cd adder |
| 18 | +cargo install --locked wasm-tools |
| 19 | +``` |
| 20 | +Install [`wasmtime`](https://github.com/bytecodealliance/wasmtime#installation): |
| 21 | +```sh |
| 22 | +curl https://wasmtime.dev/install.sh -sSf | bash |
26 | 23 | ```
|
27 | 24 |
|
28 |
| -Note that `cargo component` generates the necessary bindings as a module called `bindings`. |
29 |
| - |
30 |
| -## 3. Add the WIT world the Component will implement |
| 25 | +## 2. Scaffolding a Component |
31 | 26 |
|
32 |
| -Next, update `wit/world.wit` to match the [`adder` world][adder-wit]: |
| 27 | +We will create a component in Rust that implements the `add` function exported |
| 28 | +by the [`adder` world][docs-adder] world in the `docs:adder` |
| 29 | +[package](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#package-names). |
33 | 30 |
|
| 31 | +First, we will create a new WebAssembly component package called `add`: |
| 32 | +```sh |
| 33 | +cargo component new add --lib && cd add |
34 | 34 | ```
|
35 |
| - |
36 | 35 |
|
37 |
| -interface add { |
38 |
| - add: func(x: u32, y: u32) -> u32; |
39 |
| -} |
| 36 | +## 3. Adding the WIT world |
40 | 37 |
|
41 |
| -world adder { |
42 |
| - export add; |
43 |
| -} |
| 38 | +We now need to change our generated `wit/world.wit` to match `docs:adder`: |
| 39 | +```wit |
| 40 | +{{#include ../../examples/tutorial/wit/adder/world.wit}} |
44 | 41 | ```
|
45 | 42 |
|
46 |
| -The `component` section of `Cargo.toml` should look like the following: |
| 43 | +The `package.metadata.component` section of our `Cargo.toml` should be changed |
| 44 | +to the following: |
47 | 45 |
|
48 | 46 | ```toml
|
49 | 47 | [package.metadata.component]
|
50 | 48 | package = "docs:adder"
|
51 | 49 | ```
|
52 | 50 |
|
53 |
| -## 4. Generate bindings for our component |
| 51 | +## 4. Generating bindings |
54 | 52 |
|
55 |
| -After performing these changes, we can re-generate bindings with `cargo component bindings`: |
| 53 | +Now that we've updated our `world.wit` and `Cargo.toml`, we can re-generate |
| 54 | +bindings with the command below: |
56 | 55 |
|
57 |
| -```console |
| 56 | +```sh |
58 | 57 | cargo component bindings
|
59 | 58 | ```
|
60 | 59 |
|
61 |
| -`cargo component bindings` will generate bindings for the world specified in a package's `Cargo.toml`. In particular, |
62 |
| -`cargo component` will create a `Guest` trait that a component should implement. |
| 60 | +`cargo-component` will generate bindings for our |
| 61 | +world and create a `Guest` trait that a component should |
| 62 | +implement. |
63 | 63 |
|
64 |
| -## 5. Implement the generated `Guest` trait |
| 64 | +## 5. Implementing the `Guest` trait |
65 | 65 |
|
66 | 66 | Implement the `Guest` trait in `src/lib.rs`, using the scaffolded code. Your code should look something like the following:
|
67 | 67 |
|
68 | 68 | ```rs
|
69 |
| -#[allow(warnings)] |
70 |
| -mod bindings; |
71 |
| - |
72 |
| -use bindings::exports::docs::adder::add::Guest; |
73 |
| - |
74 |
| -struct Component; |
75 |
| - |
76 |
| -impl Guest for Component { |
77 |
| - fn add(x: u32, y: u32) -> u32 { |
78 |
| - return x + y; |
79 |
| - } |
80 |
| -} |
81 |
| - |
82 |
| -bindings::export!(Component with_types_in bindings); |
| 69 | +{{#include ../../examples/tutorial/adder/src/lib.rs}} |
83 | 70 | ```
|
84 | 71 |
|
85 |
| -## 6. Build the component |
| 72 | +## 6. Building a Component |
86 | 73 |
|
87 |
| -Now, use `cargo component` to build the component, being sure to optimize with a release build. |
| 74 | +Now, let's build our component, being sure to optimize with a release build: |
88 | 75 |
|
89 |
| -```console |
| 76 | +```sh |
90 | 77 | cargo component build --release
|
91 | 78 | ```
|
92 | 79 |
|
93 |
| -> **WARNING:** Building with `--release` removes all debug-related information from the resulting .wasm file. When prototyping or testing locally, you might want to avoid `--release` to obtain useful backtraces in case of errors (for example, with `wasmtime::WasmBacktraceDetails::Enable`). Note: the resulting .wasm file will be considerably larger (likely 4MB+). |
| 80 | +> [!WARNING] |
| 81 | +> Building with `--release` removes all debug-related information from the resulting `.wasm` file. |
| 82 | +> |
| 83 | +> When prototyping or testing locally, you might want to avoid `--release` to |
| 84 | +> obtain useful backtraces in case of errors (for example, with |
| 85 | +> [`wasmtime::WasmBacktraceDetails::Enable`](https://docs.rs/wasmtime/latest/wasmtime/enum.WasmBacktraceDetails.html#variant.Enable)). |
| 86 | +> Note: the resulting `.wasm` file will be considerably larger (likely 4MB+). |
94 | 87 |
|
95 |
| -You can use `wasm-tools component wit` to output the WIT package of the component: |
| 88 | +You can use `wasm-tools` to output the WIT package of the component: |
96 | 89 |
|
| 90 | +```sh |
| 91 | +wasm-tools component wit target/wasm32-wasip1/release/add.wasm |
97 | 92 | ```
|
98 |
| -$ wasm-tools component wit target/wasm32-wasip1/release/adder.wasm |
| 93 | + |
| 94 | +The command above should produce the output below: |
| 95 | + |
| 96 | +```wit |
99 | 97 | package root:component;
|
100 | 98 |
|
101 | 99 | world root {
|
|
108 | 106 | }
|
109 | 107 | ```
|
110 | 108 |
|
111 |
| -### Running a Component from Rust Applications |
| 109 | + |
| 110 | +### Running a Component |
112 | 111 |
|
113 | 112 | To verify that our component works, lets run it from a Rust application that knows how to run a
|
114 |
| -component targeting the [`adder` world][adder-wit]. |
| 113 | +component targeting the [`adder` world](#adding-the-wit-world). |
115 | 114 |
|
116 | 115 | The application uses [`wasmtime`](https://github.com/bytecodealliance/wasmtime) crates to generate
|
117 | 116 | Rust bindings, bring in WASI worlds, and execute the component.
|
118 | 117 |
|
119 |
| -```sh |
| 118 | +```console |
120 | 119 | $ cd examples/example-host
|
121 | 120 | $ cargo run --release -- 1 2 ../add/target/wasm32-wasip1/release/adder.wasm
|
122 | 121 | 1 + 2 = 3
|
123 | 122 | ```
|
124 | 123 |
|
125 |
| -## Importing an interface with `cargo component` |
| 124 | +## Importing an interface |
126 | 125 |
|
127 |
| -The world file (`wit/world.wit`) generated for you by `cargo component new --lib` doesn't specify any imports. |
| 126 | +The world file (`wit/world.wit`) we generated doesn't specify any imports. |
| 127 | +If your component consumes other components, you can edit the `world.wit` file to import their interfaces. |
128 | 128 |
|
129 |
| -> `cargo component build`, by default, uses the Rust `wasm32-wasi` target, and therefore automatically imports any required WASI interfaces - no action is needed from you to import these. This section is about importing custom WIT interfaces from library components. |
| 129 | +> [!NOTE] |
| 130 | +> This section is about importing custom WIT interfaces from library components. |
| 131 | +> By default, `cargo-component` imports any required [WASI interfaces](https://wasi.dev/interfaces) |
| 132 | +> for us without needing to explicitly declare them. |
130 | 133 |
|
131 |
| -If your component consumes other components, you can edit the `world.wit` file to import their interfaces. |
132 | 134 |
|
133 | 135 | For example, suppose you have created and built an adder component as explained in the [exporting an interface section](#exporting-an-interface-with-cargo-component) and want to use that component in a calculator component. Here is a partial example world for a calculator that imports the add interface:
|
134 | 136 |
|
@@ -157,7 +159,10 @@ Because the `docs:adder` package is in a different project, we must first tell `
|
157 | 159 | "docs:adder" = { path = "../adder/wit" } # directory containing the WIT package
|
158 | 160 | ```
|
159 | 161 |
|
160 |
| -Note that the path is to the adder project's WIT _directory_, not to the `world.wit` file. A WIT package may be spread across multiple files in the same directory; `cargo component` will look at all the files. |
| 162 | +> [!NOTE] |
| 163 | +> The path for `docs:adder` is relative to the `wit` _directory_, not to the `world.wit` file. |
| 164 | +> |
| 165 | +> A WIT package may be spread across multiple files in the same directory; `cargo component` will search them all. |
161 | 166 |
|
162 | 167 | ### Calling the import from Rust
|
163 | 168 |
|
@@ -283,7 +288,7 @@ As mentioned above, `cargo component build` doesn't generate a WIT file for a co
|
283 | 288 |
|
284 | 289 | 6. Run the composed component:
|
285 | 290 |
|
286 |
| - ```sh |
| 291 | + ```console |
287 | 292 | $ wasmtime run ./my-composed-command.wasm
|
288 | 293 | 1 + 1 = 579 # might need to go back and do some work on the calculator implementation
|
289 | 294 | ```
|
@@ -551,4 +556,6 @@ If you are hosting a Wasm runtime, you can export a resource from your host for
|
551 | 556 | }
|
552 | 557 | ```
|
553 | 558 |
|
554 |
| -[adder-wit]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/wit/adder/world.wit |
| 559 | +[cargo-component]: https://github.com/bytecodealliance/cargo-component |
| 560 | +[cargo-component-install]: https://github.com/bytecodealliance/cargo-component#install |
| 561 | +[docs-adder]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/wit/adder/world.wit |
0 commit comments