Exposing Resources
So far we've covered basic functions, numbers, strings, lists, records, and variants, but there's one key aspect in programming we haven't touched on yet - objects with behaviour and internal state!
In WIT, we use a resource
to give the host access to an "object" that has
behaviour without exposing how that object is implemented (or even which
language it is implemented in).
To explore this concept, we'll create a basic Calculator
resource which lets
the caller do various operations like addition and multiplication.
Like always, let's start off with a new Rust project.
$ cargo new --lib tutorial-05
The WIT File
The key thing to understand about a resource
is that it only defines methods.
// resources.wai
resource calculator {
static new: func(initial-value: float32) -> calculator
current-value: func() -> float32
add: func(value: float32)
multiply: func(value: float32)
}
Prefixing a function with the static
keyword will turn it into a
static
method. This is useful for defining constructors or
factory functions.
Resource methods also allow the async
modifier (i.e.
add: async func(value: float32)
), however that will require your runtime to
support async
functions.
Writing Some Rust
As always, we need to add wai-bindgen
as a dependency.
$ cargo add wai-bindgen-rust
We also need to ask wai-bindgen
to generate exports for our resources.wai
interface.
#![allow(unused)] fn main() { // src/lib.rs wai_bindgen_rust::export!("resources.wai"); }
If we run cargo check
, we'll see that - besides the missing Resources
type
we expected - it complains about not being able to find Calculator
.
$ cargo check
error[E0412]: cannot find type `Calculator` in module `super`
...
error[E0412]: cannot find type `Resources` in module `super`
We can create the Resources
struct and implement the resources::Resources
trait for it. This module won't have any top-level functions, so the trait
implementation can stay empty.
#![allow(unused)] fn main() { pub struct Resources; impl resources::Resources for Resources {} }
The way the resources::Calculator
trait was designed adds certain constraints
to our Calculator
struct, though.
#![allow(unused)] fn main() { pub struct Calculator(Mutex<f32>); impl resources::Calculator for Calculator { fn new(initial_value: f32) -> Handle<Calculator> { Handle::new(Calculator(Mutex::new(initial_value))) } fn current_value(&self) -> f32 { *self.0.lock().unwrap() } fn add(&self, value: f32) { *self.0.lock().unwrap() += value; } fn multiply(&self, value: f32) { *self.0.lock().unwrap() *= value; } } }
A Note On Mutability
You'll notice that all methods in the resources::Calculator
trait take an
immutable &self
. This means we'll need to use interior mutability
if we want to update internal state.
While this might feel a bit awkward, there is a very good reason for requiring all state in a resource to synchronise its mutations - WebAssembly makes no guarantees that the caller will respect the borrow checker.
Publishing
We need to publish this package to WAPM, so let's update Cargo.toml
with the
relevant metadata.
# Cargo.toml
[package]
...
description = "A basic calculator"
[lib]
crate-type = ["cdylib", "rlib"]
[package.metadata.wapm]
namespace = "wasmer"
abi = "none"
bindings = { wai-bindgen = "0.1.0", exports = "resources.wai" }
Now, we can publish it to WAPM.
$ cargo wapm
Successfully published package `wasmer/tutorial-05@0.1.0`
Using The Bindings From Python
Once the package has been published, create a new Python virtual environment and import the package into Python.
$ python -m venv env
$ source env/bin/activate
$ wapm install --pip wasmer/tutorial-05
Next, let's create our test script.
# main.py
from tutorial_05 import bindings
from tutorial_05.bindings.resources import Calculator
resources = bindings.resources()
calculator = Calculator.new(resources, 3)
calculator.add(5)
calculator.multiply(2)
print("(3 + 5)*2 =", calculator.current_value())
Running it from the command-line should show us that (3+5)*2 = 16
.
$ python main.py
(3+5)*2 = 16
Conclusion
The resource is a useful tool for exposing stateful objects that have behaviour, and should be familiar to
With the addition of resources, we've introduced most of the fundamental constructs in WIT.
Exercises for the reader:
- Expand the
calculator
resource to be a fully fledged calculator - Try to create your own Regex package using the
regex
crate