Skip to content

Commit 5c13408

Browse files
authored
Merge pull request #140 from itowlson/resources-rust
Working with records and resources in Rust
2 parents a755690 + d6f3ffc commit 5c13408

File tree

1 file changed

+263
-0
lines changed
  • component-model/src/language-support

1 file changed

+263
-0
lines changed

component-model/src/language-support/rust.md

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,266 @@ fn main() {
275275
$ wasmtime run ./my-composed-command.wasm
276276
1 + 1 = 579 # might need to go back and do some work on the calculator implementation
277277
```
278+
279+
## Using user-defined types
280+
281+
[User-defined types](../design/wit.md#user-defined-types) map to Rust types as follows.
282+
283+
| WIT type | Rust binding |
284+
|------------|--------------|
285+
| `record` | `struct` with public fields corresponding to the record fields |
286+
| `variant` | `enum` with cases corresponding to the variant cases |
287+
| `enum` | `enum` with cases corresponding to the enum cases, with no data attached |
288+
| `resource` | [See below](#using-resources) |
289+
| `flags` | Opaque type supporting bit flag operations, with constants for flag values |
290+
291+
For example, consider the following WIT:
292+
293+
```wit
294+
interface types {
295+
enum operation {
296+
add,
297+
sub,
298+
mul,
299+
div
300+
}
301+
302+
record expression {
303+
left: u32,
304+
operation: operation,
305+
right: u32
306+
}
307+
308+
eval: func(expr: expression) -> u32;
309+
}
310+
```
311+
312+
When exported from a component, this could be implemented as:
313+
314+
```rust
315+
impl Guest for Implementation {
316+
fn eval(expr: Expression) -> u32 {
317+
// Record fields become public fields on a struct
318+
let (l, r) = (expr.left, expr.right);
319+
match expr.operation {
320+
// Enum becomes an enum with only unit cases
321+
Operation::Add => l + r,
322+
Operation::Sub => l - r,
323+
Operation::Mul => l * r,
324+
Operation::Div => l / r,
325+
}
326+
}
327+
}
328+
```
329+
330+
## Using resources
331+
332+
[Resources](../design/wit.md#resources) are handles to entities that live outside the component, for example in a host, or in a different component.
333+
334+
### Example
335+
336+
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:
337+
338+
```wit
339+
package docs:[email protected];
340+
341+
interface types {
342+
enum operation {
343+
add,
344+
sub,
345+
mul,
346+
div
347+
}
348+
349+
resource engine {
350+
constructor();
351+
push-operand: func(operand: u32);
352+
push-operation: func(operation: operation);
353+
execute: func() -> u32;
354+
}
355+
}
356+
357+
world calculator {
358+
export types;
359+
}
360+
```
361+
362+
### Implementing and exporting a resource in a component
363+
364+
To implement the calculator using `cargo component`:
365+
366+
1. Create a library component as shown in previous sections, with the WIT given above.
367+
368+
2. Define a Rust `struct` to represent the calculator state:
369+
370+
```rust
371+
use std::cell::RefCell;
372+
373+
struct CalcEngine {
374+
stack: RefCell<Vec<u32>>,
375+
}
376+
```
377+
378+
> 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>>`.
379+
380+
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:
381+
382+
```rust
383+
use bindings::exports::docs::rpn::types::{GuestEngine, Operation};
384+
385+
impl GuestEngine for CalcEngine {
386+
fn new() -> Self {
387+
CalcEngine {
388+
stack: RefCell::new(vec![])
389+
}
390+
}
391+
392+
fn push_operand(&self, operand: u32) {
393+
self.stack.borrow_mut().push(operand);
394+
}
395+
396+
fn push_operation(&self, operation: Operation) {
397+
let mut stack = self.stack.borrow_mut();
398+
let right = stack.pop().unwrap(); // TODO: error handling!
399+
let left = stack.pop().unwrap();
400+
let result = match operation {
401+
Operation::Add => left + right,
402+
Operation::Sub => left - right,
403+
Operation::Mul => left * right,
404+
Operation::Div => left / right,
405+
};
406+
stack.push(result);
407+
}
408+
409+
fn execute(&self) -> u32 {
410+
self.stack.borrow_mut().pop().unwrap() // TODO: error handling!
411+
}
412+
}
413+
```
414+
415+
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:
416+
417+
```rust
418+
struct Implementation;
419+
impl Guest for Implementation {
420+
type Engine = CalcEngine;
421+
}
422+
423+
bindings::export!(Implementation with_types_in bindings);
424+
```
425+
426+
This completes the implementation of the calculator `engine` resource. Run `cargo component build` to create a component `.wasm` file.
427+
428+
### Importing and consuming a resource in a component
429+
430+
To use the calculator engine in another component, that component must import the resource.
431+
432+
1. Create a command component as shown in previous sections.
433+
434+
2. Add a `wit/world.wit` to your project, and write a WIT world that imports the RPN calculator types:
435+
436+
```wit
437+
package docs:rpn-cmd;
438+
439+
world app {
440+
import docs:rpn/[email protected];
441+
}
442+
```
443+
444+
3. Edit `Cargo.toml` to tell `cargo component` about the new WIT file and the external RPN package file:
445+
446+
```toml
447+
[package.metadata.component]
448+
package = "docs:rpn-cmd"
449+
450+
[package.metadata.component.target]
451+
path = "wit"
452+
453+
[package.metadata.component.target.dependencies]
454+
"docs:rpn" = { path = "../wit" } # or wherever your resource WIT is
455+
```
456+
457+
4. The resource now appears in the generated bindings as a `struct`, with appropriate associated functions. Use these to construct a test app:
458+
459+
```rust
460+
#[allow(warnings)]
461+
mod bindings;
462+
use bindings::docs::rpn::types::{Engine, Operation};
463+
464+
fn main() {
465+
let calc = Engine::new();
466+
calc.push_operand(1);
467+
calc.push_operand(2);
468+
calc.push_operation(Operation::Add);
469+
let sum = calc.execute();
470+
println!("{sum}");
471+
}
472+
```
473+
474+
You can now build the command component and [compose it with the `.wasm` component that implements the resource.](../creating-and-consuming/composing.md). You can then run the composed command with `wasmtime run`.
475+
476+
### Implementing and exporting a resource implementation in a host
477+
478+
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.
479+
480+
1. Use `wasmtime::component::bindgen!` to specify the WIT you are a host for:
481+
482+
```rust
483+
wasmtime::component::bindgen!({
484+
path: "../wit"
485+
});
486+
```
487+
488+
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:
489+
490+
```rust
491+
wasmtime::component::bindgen!({
492+
path: "../wit",
493+
with: {
494+
"docs:rpn/types/engine": CalcEngine,
495+
}
496+
});
497+
```
498+
499+
> 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.
500+
501+
3. If the representation type isn't a built-in type, define it:
502+
503+
```rust
504+
struct CalcEngine { /* ... */ }
505+
```
506+
507+
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:
508+
509+
```rust
510+
impl docs::rpn::types::HostEngine for MyHost {
511+
fn new(&mut self) -> wasmtime::component::Resource<docs::rpn::types::Engine> { /* ... */ }
512+
fn push_operand(&mut self, self_: wasmtime::component::Resource<docs::rpn::types::Engine>) { /* ... */ }
513+
// etc.
514+
}
515+
```
516+
517+
**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`.
518+
519+
5. Add a `wasmtime::component::ResourceTable` to the host:
520+
521+
```rust
522+
struct MyHost {
523+
calcs: wasmtime::component::ResourceTable,
524+
}
525+
```
526+
527+
6. In your resource method implementations, use this table to store and access instances of the resource representation:
528+
529+
```rust
530+
impl docs::rpn::types::HostEngine for MyHost {
531+
fn new(&mut self) -> wasmtime::component::Resource<docs::rpn::types::Engine> {
532+
self.calcs.push(CalcEngine::new()).unwrap() // TODO: error handling
533+
}
534+
fn push_operand(&mut self, self_: wasmtime::component::Resource<docs::rpn::types::Engine>) {
535+
let calc_engine = self.calcs.get(&self_).unwrap();
536+
// calc_engine is a CalcEngine - call its functions
537+
}
538+
// etc.
539+
}
540+
```

0 commit comments

Comments
 (0)