Skip to content

Commit c58c2b6

Browse files
chore(lang/rust): fill out more of runnable component section
1 parent dff85bd commit c58c2b6

File tree

2 files changed

+160
-2
lines changed

2 files changed

+160
-2
lines changed

component-model/src/creating-runnable-components.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ This section contains language-specific guides on how to create runnable compone
55
At a high level there are at least two ways to create components that are more like binaries than libraries
66
(i.e. that are easy to run from a tool like `wasmtime`):
77

8-
- Creating a "command" component
9-
- Exporting the `wasi:cli/run` interface
8+
1. Exporting the `wasi:cli/run` interface (recommended)
9+
2. Creating a "command" component
1010

1111
This section explores how to do the above in relevant languages.
1212

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,160 @@
11
# Creating Runnable Components (Rust)
22

3+
## Exporting the `wasi:cli/run` interface
4+
5+
Any reactor (library-like) component can *also* export the [`run` interface]wasi-cli-iface-run] inside [WASI CLI][wasi-cli],
6+
and signal to ecosystem projects that it can be executed.
7+
8+
> [!WARNING]
9+
> Reactor components can be reused, and while most platforms may *not* choose to reuse a component after `wasi:cli/run`
10+
> has been called, there is no guarantee that they will or will not.
11+
12+
### 1. Create a new Rust library project
13+
14+
To build a simple component that exports `wasi:cli/run`, first create a new Rust project:
15+
16+
```sh
17+
cargo new --lib runnable-example
18+
```
19+
20+
After creating the new project, ensure it is a `cdylib` crate by updating `runnable-example/Cargo.toml` and adding
21+
the following lines:
22+
23+
```toml
24+
[lib]
25+
crate-type = ['cdylib']
26+
```
27+
28+
We'll also be generating Rust bindings from WIT interfaces, so add `wit-bindgen`:
29+
30+
```sh
31+
cargo add wit-bindgen
32+
```
33+
34+
### 2. Add the appropriate WIT interfaces
35+
36+
Then, add the appropriate WIT interfaces. For example a simple component that prints "Hello World", add the following
37+
contents to `runnable-example/wit/component.wit`:
38+
39+
```wit
40+
package example:runnable;
41+
42+
interface greet {
43+
greet: func(name: string) -> string;
44+
}
45+
46+
world greeter {
47+
export greet;
48+
export wasi:cli/run@0.2.7;
49+
}
50+
```
51+
52+
Building a library component this way does two things:
53+
54+
- Enables *other* components/hosts to use the `greet` interface
55+
- Exposes an interface (`wasi:cli/run`) that indicates this component can be run like a CLI
56+
- Note that no guarantees are made about what the component *does* when it runs
57+
58+
While we created `greet`, `wasi:cli` is a well-known interface. We can resolve this interface to local WIT by
59+
using `wkg`:
60+
61+
```sh
62+
wkg wit fetch
63+
```
64+
65+
At this point, you should have a `wit` folder with a `deps` subfolder and yoru original `component.wit`.
66+
67+
[!WARNING]: #
68+
69+
### 3. Write the code for the component
70+
71+
The following code can be inserted into `runnable-example/src/lib.rs`:
72+
73+
```rust
74+
mod bindings {
75+
wit_bindgen::generate!()
76+
}
77+
78+
79+
package example:runnable;
80+
81+
interface greet {
82+
greet: func(name: string) -> string;
83+
}
84+
85+
world greeter {
86+
export greet;
87+
export wasi:cli/run@0.2.7;
88+
}
89+
90+
/// Component off of which implementation will hang (this can be named anything)
91+
struct Component;
92+
93+
impl Component {
94+
fn greet(s: impl AsRef<str>) -> String {
95+
format!("hello {s}!");
96+
}
97+
}
98+
99+
export bindings::example::runnable::greet::Guest for Component {
100+
fn greet(&self, s: String) -> String {
101+
self.greet(s)
102+
}
103+
}
104+
105+
export bindings::wasi::cli::run::Guest for Component {
106+
fn run(&self) -> Result<(), ()> {
107+
// NOTE: here, we would normally use more of the wasi:cli interface
108+
// to grab arguments and other information from the execution environment.
109+
eprintln!("CLI => {}", self.greet("CLI User"));
110+
Ok(())
111+
}
112+
}
113+
```
114+
115+
### 4. Build the component
116+
117+
To build the component, use `cargo`:
118+
119+
```sh
120+
cargo build --target=wasm32-wasip2
121+
```
122+
123+
### 5. Run the component with `wasmtime`
124+
125+
You can run the component with `wasmtime`, and unlike a generic reactor component, you do not need to specify
126+
the interface and function to run (`wasi:cli/run` is detected and used automatically):
127+
128+
```console
129+
$ wasmtime run target/wasm32-wasip2/runnable-example.wasm
130+
CLI => hello CLI User!
131+
```
132+
133+
## Creating a command component
134+
135+
A _command_ is a component with a specific export that allows it to be executed directly by `wasmtime` (or other `wasi:cli` hosts). In Rust terms, it's the equivalent of an application (`bin`) package with a `main` function, instead of a library crate (`lib`) package.
136+
137+
To create a command with cargo component, run:
138+
139+
```sh
140+
cargo component new <name>
141+
```
142+
143+
Unlike library components, this does _not_ have the `--lib` flag. You will see that the created project is different too:
144+
145+
- It doesn't contain a `.wit` file. `cargo component build` will automatically export the `wasi:cli/run` interface for Rust `bin` packages, and hook it up to `main`.
146+
- Because there's no `.wit` file, `Cargo.toml` doesn't contain a `package.metadata.component.target` section.
147+
- The Rust file is called `main.rs` instead of `lib.rs`, and contains a `main` function instead of an interface implementation.
148+
149+
You can write Rust in this project, just as you normally would, including importing your own or third-party crates.
150+
151+
> All the crates that make up your project are linked together at build time, and compiled to a _single_ Wasm component. In this case, all the linking is happening at the Rust level: no WITs or component composition is involved. Only if you import Wasm interfaces do WIT and composition come into play.
152+
153+
To run your command component:
154+
155+
```sh
156+
cargo component build
157+
wasmtime run ./target/wasm32-wasip1/debug/<name>.wasm
158+
```
159+
160+
> **WARNING:** If your program prints to standard out or error, you may not see the printed output! Some versions of `wasmtime` have a bug where they don't flush output streams before exiting. To work around this, add a `std::thread::sleep()` with a 10 millisecond delay before exiting `main`.

0 commit comments

Comments
 (0)