Skip to content

Commit 69ac5e7

Browse files
chore(lang/rust): add old cargo component implementations
1 parent 54b2731 commit 69ac5e7

File tree

1 file changed

+217
-0
lines changed
  • component-model/src/language-support/using-wit-resources

1 file changed

+217
-0
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,218 @@
11
# Using WIT Resources (Rust)
2+
3+
[Resources](../design/wit.md#resources) are handles to entities that live outside the component (i.e. in a host, or other component).
4+
5+
## An example stack-based calculator
6+
7+
In this section, our example resource will be a [Reverse Polish Notation (RPN)](https://en.wikipedia.org/wiki/Reverse_Polish_notation) calculator. (Engineers of a certain vintage will remember this from handheld calculators of the 1970s.) A RPN calculator is a stateful entity: a consumer pushes operands and operations onto a stack maintained within the calculator, then evaluates the stack to produce a value. The resource in WIT looks like this:
8+
9+
```wit
10+
package docs:rpn@0.1.0;
11+
12+
interface types {
13+
enum operation {
14+
add,
15+
sub,
16+
mul,
17+
div
18+
}
19+
20+
resource engine {
21+
constructor();
22+
push-operand: func(operand: u32);
23+
push-operation: func(operation: operation);
24+
execute: func() -> u32;
25+
}
26+
}
27+
28+
world calculator {
29+
export types;
30+
}
31+
```
32+
33+
## Implementing and exporting a resource in a component
34+
35+
To implement the calculator using `cargo component`:
36+
37+
1. Create a library component as shown in previous sections, with the WIT given above.
38+
39+
2. Define a Rust `struct` to represent the calculator state:
40+
41+
```rust
42+
use std::cell::RefCell;
43+
44+
struct CalcEngine {
45+
stack: RefCell<Vec<u32>>,
46+
}
47+
```
48+
49+
> Why is the stack wrapped in a `RefCell`? As we will see, the generated Rust trait for the calculator engine has _immutable_ references to `self`. But our implementation of that trait will need to mutate the stack. So we need a type that allows for interior mutability, such as `RefCell<T>` or `Arc<RwLock<T>>`.
50+
51+
3. The generated bindings (`bindings.rs`) for an exported resource include a trait named `GuestX`, where `X` is the resource name. (You may need to run `cargo component build` to regenerate the bindings after updating the WIT.) For the calculator `engine` resource, the trait is `GuestEngine`. Implement this trait on the `struct` from step 2:
52+
53+
```rust
54+
use bindings::exports::docs::rpn::types::{GuestEngine, Operation};
55+
56+
impl GuestEngine for CalcEngine {
57+
fn new() -> Self {
58+
CalcEngine {
59+
stack: RefCell::new(vec![])
60+
}
61+
}
62+
63+
fn push_operand(&self, operand: u32) {
64+
self.stack.borrow_mut().push(operand);
65+
}
66+
67+
fn push_operation(&self, operation: Operation) {
68+
let mut stack = self.stack.borrow_mut();
69+
let right = stack.pop().unwrap(); // TODO: error handling!
70+
let left = stack.pop().unwrap();
71+
let result = match operation {
72+
Operation::Add => left + right,
73+
Operation::Sub => left - right,
74+
Operation::Mul => left * right,
75+
Operation::Div => left / right,
76+
};
77+
stack.push(result);
78+
}
79+
80+
fn execute(&self) -> u32 {
81+
self.stack.borrow_mut().pop().unwrap() // TODO: error handling!
82+
}
83+
}
84+
```
85+
86+
4. We now have a working calculator type which implements the `engine` contract, but we must still connect that type to the `engine` resource type. This is done by implementing the generated `Guest` trait. For this WIT, the `Guest` trait contains nothing except an associated type. You can use an empty `struct` to implement the `Guest` trait on. Set the associated type for the resource - in our case, `Engine` - to the type which implements the resource trait - in our case, the `CalcEngine` `struct` which implements `GuestEngine`. Then use the `export!` macro to export the mapping:
87+
88+
```rust
89+
struct Implementation;
90+
impl Guest for Implementation {
91+
type Engine = CalcEngine;
92+
}
93+
94+
bindings::export!(Implementation with_types_in bindings);
95+
```
96+
97+
This completes the implementation of the calculator `engine` resource. Run `cargo component build` to create a component `.wasm` file.
98+
99+
## Importing and consuming a resource in a component
100+
101+
To use the calculator engine in another component, that component must import the resource.
102+
103+
1. Create a command component as shown in previous sections.
104+
105+
2. Add a `wit/world.wit` to your project, and write a WIT world that imports the RPN calculator types:
106+
107+
```wit
108+
package docs:rpn-cmd;
109+
110+
world app {
111+
import docs:rpn/types@0.1.0;
112+
}
113+
```
114+
115+
3. Edit `Cargo.toml` to tell `cargo component` about the new WIT file and the external RPN package file:
116+
117+
```toml
118+
[package.metadata.component]
119+
package = "docs:rpn-cmd"
120+
121+
[package.metadata.component.target]
122+
path = "wit"
123+
124+
[package.metadata.component.target.dependencies]
125+
"docs:rpn" = { path = "../wit" } # or wherever your resource WIT is
126+
```
127+
128+
4. The resource now appears in the generated bindings as a `struct`, with appropriate associated functions. Use these to construct a test app:
129+
130+
```rust
131+
#[allow(warnings)]
132+
mod bindings;
133+
use bindings::docs::rpn::types::{Engine, Operation};
134+
135+
fn main() {
136+
let calc = Engine::new();
137+
calc.push_operand(1);
138+
calc.push_operand(2);
139+
calc.push_operation(Operation::Add);
140+
let sum = calc.execute();
141+
println!("{sum}");
142+
}
143+
```
144+
145+
You can now build the command component and [compose it with the `.wasm` component that implements the resource.](../composing-and-distributing/composing.md). You can then run the composed command with `wasmtime run`.
146+
147+
## Implementing and exporting a resource implementation in a host
148+
149+
If you are hosting a Wasm runtime, you can export a resource from your host for guests to consume. Hosting a runtime is outside the scope of this book, so we will give only a broad outline here. This is specific to the Wasmtime runtime; other runtimes may express things differently.
150+
151+
1. Use `wasmtime::component::bindgen!` to specify the WIT you are a host for:
152+
153+
```rust
154+
wasmtime::component::bindgen!({
155+
path: "../wit"
156+
});
157+
```
158+
159+
2. Tell `bindgen!` how you will represent the resource in the host via the `with` field. This can be any Rust type. For example, the RPN engine could be represented by a `CalcEngine` struct:
160+
161+
```rust
162+
wasmtime::component::bindgen!({
163+
path: "../wit",
164+
with: {
165+
"docs:rpn/types/engine": CalcEngine,
166+
}
167+
});
168+
```
169+
170+
> If you don't specify the host representation for a resource, it defaults to an empty enum. This is rarely useful as resources are usually stateful.
171+
172+
3. If the representation type isn't a built-in type, define it:
173+
174+
```rust
175+
struct CalcEngine { /* ... */ }
176+
```
177+
178+
4. As a host, you will already be implementing a `Host` trait. You will now need to implement a `HostX` trait (where `X` is the resource name) _on the same type_ as the `Host` trait:
179+
180+
```rust
181+
impl docs::rpn::types::HostEngine for MyHost {
182+
fn new(&mut self) -> wasmtime::component::Resource<docs::rpn::types::Engine> { /* ... */ }
183+
fn push_operand(&mut self, self_: wasmtime::component::Resource<docs::rpn::types::Engine>) { /* ... */ }
184+
// etc.
185+
}
186+
```
187+
188+
> **Important:** You implement this on the 'overall' host type, *not* on the resource representation! Therefore, the `self` reference in these functions is to the 'overall' host type. For instance methods of the resource, the instance is identified by a second parameter (`self_`), of type `wasmtime::component::Resource`.
189+
190+
5. Add a `wasmtime::component::ResourceTable` to the host:
191+
192+
```rust
193+
struct MyHost {
194+
calcs: wasmtime::component::ResourceTable,
195+
}
196+
```
197+
198+
6. In your resource method implementations, use this table to store and access instances of the resource representation:
199+
200+
```rust
201+
impl docs::rpn::types::HostEngine for MyHost {
202+
fn new(&mut self) -> wasmtime::component::Resource<docs::rpn::types::Engine> {
203+
self.calcs.push(CalcEngine::new()).unwrap() // TODO: error handling
204+
}
205+
fn push_operand(&mut self, self_: wasmtime::component::Resource<docs::rpn::types::Engine>) {
206+
let calc_engine = self.calcs.get(&self_).unwrap();
207+
// calc_engine is a CalcEngine - call its functions
208+
}
209+
// etc.
210+
}
211+
```
212+
213+
[cargo-component]: https://github.com/bytecodealliance/cargo-component
214+
[cargo-component-install]: https://github.com/bytecodealliance/cargo-component#install
215+
[docs-adder]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/wit/adder/world.wit
216+
217+
[!NOTE]: #
218+
[!WARNING]: #

0 commit comments

Comments
 (0)