Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Clarify setup for "Rust Components" tutorial #218

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion component-model/examples/example-host/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ $ cargo run --release -- 1 2 add.wasm
```

> [!NOTE]
> `add.wasm` is available in thsi folder, but can be replaced with your own built WebAssembly component
> `add.wasm` is available in this folder, but can be replaced with your own built WebAssembly component
> at any time (written in any language that supports WebAssembly Components), given that it satisfies
> the `adder` world described above.

Expand Down
3 changes: 2 additions & 1 deletion component-model/examples/tutorial/adder/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#[allow(warnings)]
mod bindings;

// Separating out the interface puts it in a sub-module
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this comment makes sense, because someone coming to this would probably think "as opposed to what?" and we don't have an example of exporting the function directly anymore.

Maybe this comment could be expanded to a more general statement about bindings pathing (explaining the module hierarchy a little bit -- exports because it's an export, docs being the namepace, adder being the package, etc)

use bindings::exports::docs::adder::add::Guest;

struct Component;

impl Guest for Component {
fn add(x: u32, y: u32) -> u32 {
a + b
x + y
}
}

Expand Down
4 changes: 2 additions & 2 deletions component-model/src/language-support/javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ With `jco transpile` any WebAssembly binary (compiled from any language) can be

Reactor components are WebAssembly components that are long running and meant to be called repeatedly over time. They're analogous to libraries of functionality rather than an executable (a "command" component).

Components expose their interfaces via [WebAssembly Interface Types][docs-wit], hand-in-hand with the [Component Model][docs-component-model] which enables components to use higher level types interchangably.
Components expose their interfaces via [WebAssembly Interface Types][docs-wit], hand-in-hand with the [Component Model][docs-component-model] which enables components to use higher level types interchangeably.


[docs-wit]: ../design/wit.md
Expand Down Expand Up @@ -312,7 +312,7 @@ You should see output like the following:
OK Successfully written string-reverse.wasm.
```

Now that we have a WebAssembly binary, we can *also* use `jco` to run it in a native JavaScript context by *transpiling* the WebAsssembly binary (which could have come from anywhere!) to a JavaScript module.
Now that we have a WebAssembly binary, we can *also* use `jco` to run it in a native JavaScript context by *transpiling* the WebAssembly binary (which could have come from anywhere!) to a JavaScript module.

```console
npx jco transpile string-reverse.wasm -o dist/transpiled
Expand Down
182 changes: 89 additions & 93 deletions component-model/src/language-support/rust.md
Original file line number Diff line number Diff line change
@@ -1,99 +1,103 @@
# Components in Rust

Rust has first-class support for the component model via [the `cargo component` tool](https://github.com/bytecodealliance/cargo-component).
Rust has first-class support for the component model via the [`cargo-component`
tool][cargo-component]. We will be using
the `cargo component` subcommand to create WebAssembly components using Rust as
the component's implementation language.

`cargo component` is is a `cargo` subcommand for creating WebAssembly components
using Rust as the component's implementation language.
> [!NOTE]
> You can find more details about `cargo-component` on [crates.io](https://crates.io/crates/cargo-component).

## 1. Installing `cargo component`

To install `cargo component`, run:
## Setup

Install [`cargo-component`][cargo-component-install]:
```sh
cargo install cargo-component
cargo install --locked cargo-component
```

> You can find more details about `cargo component` in its [crates.io page](https://crates.io/crates/cargo-component).

## 2. Scaffold a Component with `cargo component`

Create a Rust library that implements the `add` function in the [`adder`world][adder-wit].

First scaffold a project:

Install [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools#installation):
```sh
$ cargo component new add --lib && cd add
cargo install --locked wasm-tools
```
Install [`wasmtime`](https://github.com/bytecodealliance/wasmtime#installation):
```sh
curl https://wasmtime.dev/install.sh -sSf | bash
```
Clone the [component-docs](https://github.com/bytecodealliance/component-docs) repo:
```sh
git clone https://github.com/bytecodealliance/component-docs
```

Note that `cargo component` generates the necessary bindings as a module called `bindings`.
## Scaffolding a Component

## 3. Add the WIT world the Component will implement
We will create a component in Rust that implements the `add` function exported
by the [`docs:adder/adder`][docs-adder] world in the
`docs:adder`
[package](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#package-names).

Next, update `wit/world.wit` to match `add.wit`:
First `cd` into the `tutorial` directory found in the repo we just cloned:
```sh
cd component-docs/component-model/examples/tutorial
```

Now create a new WebAssembly component package called `add`:
```sh
cargo component new add --lib && cd add
```
package docs:[email protected];

interface add {
add: func(x: u32, y: u32) -> u32;
}
## Adding the WIT world

world adder {
export add;
}
We now need to change our generated `wit/world.wit` to match `docs:adder`:
```wit
{{#include ../../examples/tutorial/wit/adder/world.wit}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic, this will save a ton of work!

```

The `component` section of `Cargo.toml` should look like the following:
The `package.metadata.component` section of our `Cargo.toml` should be changed
to the following:

```toml
[package.metadata.component]
package = "docs:adder"
```

## 4. Generate bindings for our component
## Generating bindings

After performing these changes, we can re-generate bindings with `cargo component bindings`:
Now that we've updated our `world.wit` and `Cargo.toml`, we can re-generate
bindings with the command below:

```console
```sh
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console here -- I believe this was the change you made in the other PR as well

cargo component bindings
```

`cargo component bindings` will generate bindings for the world specified in a package's `Cargo.toml`. In particular,
`cargo component` will create a `Guest` trait that a component should implement.
`cargo-component` will generate bindings for our
world and create a `Guest` trait that a component should
implement

## 5. Implement the generated `Guest` trait
## Implementing the `Guest` trait
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vados-cosmonic I've removed the numbering for the headers to simplify anchor link generation, happy to add them back in.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love feedback from @kate-goldenring here -- I think it's mostly a style thing. I think numbers help people refer to steps (ex. "please see step 3") but I could definitely go the other way.

That said, if we want to remove the numbers here, we should remove them everywhere (which isn't a hard change either, happy to help here or in a follow up if you'd rather not do it).

Thanks for working on this! 🙇

Copy link
Author

@mkatychev mkatychev Mar 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I was mostly following style patterns off of the rust book, including using present continuous tense for titles, ex: including v.s. include.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this makes sense... Maybe what we want is actually demarcation at a higher level (ex. in the Rust book the sections are clearly demarcated in the ToC on the left), then we can go with continuous tense and still be able to refer to things?

Happy either way here, I think the Rust book is an excellent example to follow so maybe we should change to look more like them, even if I'm partial to "steps".

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind elaborating in your 'including demarcation' comment?

Copy link
Contributor

@vados-cosmonic vados-cosmonic Mar 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I was referring to this style of numbering in the Rust book:

image

So when someone wants to mention a specific section they can refer "Section 3.3"

Copy link
Author

@mkatychev mkatychev Mar 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into this a bit and it looks like adding subchapters based on anchor links is still an open issue:
rust-lang/mdBook#167

the component docs currently use numbered subchapters, however they're currently broken up by language:
Screenshot 2025-03-25 at 12 19 50

We could break up the subchapters into their own markdown page so for example src/language-support/rust-scaffolding-component.md would become:

8.6.1 Scaffolding a Component

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another problem with having numbered headers is that any external references to the section (such as https://component-model.bytecodealliance.org/language-support/rust.html#6-build-the-component) will become broken if the section gets reordered. I wonder if there's a cleaner way of doing header numbering in the same way as numbered lists where the numbering gets automatically reordered:

1. installing
1. scaffolding
1. building
1. running

Codeblock above produces this:

  1. installing
  2. scaffolding
  3. building
  4. running

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmnnn OK, rather than make any large changes in this PR, let's maintain consistency at least then discuss it -- It looks like there are a bunch of options here, and we're probably not the only two people who want to chat it out!


Implement the `Guest` trait in `src/lib.rs`, using the scaffolded code. Your code should look something like the following:
Implement the `Guest` trait in `src/lib.rs`, using the scaffolded code. Your
code should look something like the following:

```rs
#[allow(warnings)]
mod bindings;

use bindings::exports::docs::adder::add::Guest;

struct Component;

impl Guest for Component {
fn add(x: u32, y: u32) -> u32 {
return x + y;
}
}

bindings::export!(Component with_types_in bindings);
{{#include ../../examples/tutorial/adder/src/lib.rs}}
```

## 6. Build the component
## Building a Component

Now, use `cargo component` to build the component, being sure to optimize with a release build.
Now, let's build our component, being sure to optimize with a release build:

```console
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console langtype seems to produce less syntax highlighting than sh.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah so my stance on that is that writing into a console is not normally syntax highlighted either, so it matches.

If we have a shell script, or a bash script, then it makes sense to use those, but my stance here is that if it's a console command you're supposed to enter there are other ways to make it more readable:

something \
    --like this \
    --rather-than console

and if it's code, then it should be highlighted.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done it in this PR: #232

```sh
cargo component build --release
```

You can use `wasm-tools component wit` to output the WIT package of the component:
You can use `wasm-tools` to inspect the WIT package generated by `cargo-component`:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't quite right -- we're actually inspecting the built component itself, which happens to contain the WIT that was used to create it. I think the old wording was right here.


```sh
wasm-tools component wit target/wasm32-wasip1/release/add.wasm
```
$ wasm-tools component wit target/wasm32-wasip1/release/add.wasm

The command above should produce the output below:

```wit
package root:component;

world root {
Expand All @@ -106,10 +110,10 @@ package docs:[email protected] {
}
```

### Running a Component from Rust Applications
### Running a Component

To verify that our component works, lets run it from a Rust application that knows how to run a
component targeting the [`adder` world][adder-wit].
component targeting the [`docs:adder/adder`](#adding-the-wit-world) world.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure being explicit about the namespace and package herehelps -- not everyone knows the more qualified naming schemes for WIT interfaces. Someone might think "what is the docs:adder/adder world?", and in the linked section we refer to it just as docs:adder (no mention of the world).

I think we want people to think in terms of worlds, which import/export interfaces -- the namespace and package are much less important IMO.


The application uses [`wasmtime`](https://github.com/bytecodealliance/wasmtime) crates to generate
Rust bindings, bring in WASI worlds, and execute the component.
Expand All @@ -120,46 +124,31 @@ $ cargo run --release -- 1 2 ../add/target/wasm32-wasip1/release/add.wasm
1 + 2 = 3
```

## Exporting an interface with `cargo component`

The [sample `add.wit` file](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host/add.wit) exports a function. However, you'll often prefer to export an interface, either to comply with an existing specification or to capture a set of functions and types that tend to go together. For example, to implement the following world:

```wit
package docs:[email protected];

interface add {
add: func(x: u32, y: u32) -> u32;
}
## Exporting an interface

world adder {
export add;
}
```
Notice how our `root` world in the `wasm-tools` output exports `add` as part of an _interface_.
It's often preferable to export an interface rather than a function, either to
comply with an existing specification or to capture several functions and types
at once.

you would write the following Rust code:
For example, to implement the [`docs:adder/adder`](#adding-the-wit-world)
world, you would write the following Rust code:

```rust
mod bindings;

// Separating out the interface puts it in a sub-module
use bindings::exports::docs::adder::add::Guest;

struct Component;

impl Guest for Component {
fn add(x: u32, y: u32) -> u32 {
a + b
}
}
{{#include ../../examples/tutorial/adder/src/lib.rs}}
Copy link
Author

@mkatychev mkatychev Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not against reverting the #includes, the tradeoff here is that the raw markdown is less readable but this will likely result in less code drift overall.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the includes -- certainly saves us time 👍

```

## Importing an interface with `cargo component`
## Importing an interface

The world file (`wit/world.wit`) generated for you by `cargo component new --lib` doesn't specify any imports.
The world file (`wit/world.wit`) we generated doesn't specify any imports. If
your component consumes other components, you can edit the `world.wit` file to
import their interfaces.

> `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.
> [!NOTE]
> This section is about importing custom WIT interfaces from library components.
> By default, `cargo-component` imports any required [WASI interfaces](https://wasi.dev/interfaces)
> for us without needing to explicitly declare them.

If your component consumes other components, you can edit the `world.wit` file to import their interfaces.

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:

Expand All @@ -181,14 +170,19 @@ world calculator {

### Referencing the package to import

Because the `docs:adder` package is in a different project, we must first tell `cargo component` how to find it. To do this, add the following to the `Cargo.toml` file:
Because the `docs:adder` package is in a different project, we must first tell
`cargo component` how to find it. To do this, add the following to the
`Cargo.toml` file:

```toml
[package.metadata.component.target.dependencies]
"docs:adder" = { path = "../adder/wit" } # directory containing the WIT package
"docs:adder" = { path = "../wit/adder" } # directory containing the WIT package
```

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.
> [!NOTE]
> The path for `docs:adder` is relative to the `wit` _directory_, not to the `world.wit` file.
>
> A WIT package may be spread across multiple files in the same directory; `cargo component` will search them all.

### Calling the import from Rust

Expand Down Expand Up @@ -292,8 +286,8 @@ As mentioned above, `cargo component build` doesn't generate a WIT file for a co

```toml
[package.metadata.component.target.dependencies]
"docs:calculator" = { path = "../calculator/wit" }
"docs:adder" = { path = "../adder/wit" }
"docs:calculator" = { path = "../wit/calculator" }
"docs:adder" = { path = "../wit/adder" }
```

> If the external package refers to other packages, you need to provide the paths to them as well.
Expand Down Expand Up @@ -581,4 +575,6 @@ If you are hosting a Wasm runtime, you can export a resource from your host for
}
```

[adder-wit]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/wit/adder/world.wit
[cargo-component]: https://github.com/bytecodealliance/cargo-component
[cargo-component-install]: https://github.com/bytecodealliance/cargo-component#install
[docs-adder]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/wit/adder/world.wit