Files
wasmtime/docs/lang-rust.md
Alex Crichton 2697a18d2f Redo the statically typed Func API (#2719)
* Redo the statically typed `Func` API

This commit reimplements the `Func` API with respect to statically typed
dispatch. Previously `Func` had a `getN` and `getN_async` family of
methods which were implemented for 0 to 16 parameters. The return value
of these functions was an `impl Fn(..)` closure with the appropriate
parameters and return values.

There are a number of downsides with this approach that have become
apparent over time:

* The addition of `*_async` doubled the API surface area (which is quite
  large here due to one-method-per-number-of-parameters).
* The [documentation of `Func`][old-docs] are quite verbose and feel
  "polluted" with all these getters, making it harder to understand the
  other methods that can be used to interact with a `Func`.
* These methods unconditionally pay the cost of returning an owned `impl
  Fn` with a `'static` lifetime. While cheap, this is still paying the
  cost for cloning the `Store` effectively and moving data into the
  closed-over environment.
* Storage of the return value into a struct, for example, always
  requires `Box`-ing the returned closure since it otherwise cannot be
  named.
* Recently I had the desire to implement an "unchecked" path for
  invoking wasm where you unsafely assert the type signature of a wasm
  function. Doing this with today's scheme would require doubling
  (again) the API surface area for both async and synchronous calls,
  further polluting the documentation.

The main benefit of the previous scheme is that by returning a `impl Fn`
it was quite easy and ergonomic to actually invoke the function. In
practice, though, examples would often have something akin to
`.get0::<()>()?()?` which is a lot of things to interpret all at once.
Note that `get0` means "0 parameters" yet a type parameter is passed.
There's also a double function invocation which looks like a lot of
characters all lined up in a row.

Overall, I think that the previous design is starting to show too many
cracks and deserves a rewrite. This commit is that rewrite.

The new design in this commit is to delete the `getN{,_async}` family of
functions and instead have a new API:

    impl Func {
        fn typed<P, R>(&self) -> Result<&Typed<P, R>>;
    }

    impl Typed<P, R> {
        fn call(&self, params: P) -> Result<R, Trap>;
        async fn call_async(&self, params: P) -> Result<R, Trap>;
    }

This should entirely replace the current scheme, albeit by slightly
losing ergonomics use cases. The idea behind the API is that the
existence of `Typed<P, R>` is a "proof" that the underlying function
takes `P` and returns `R`. The `Func::typed` method peforms a runtime
type-check to ensure that types all match up, and if successful you get
a `Typed` value. Otherwise an error is returned.

Once you have a `Typed` then, like `Func`, you can either `call` or
`call_async`. The difference with a `Typed`, however, is that the
params/results are statically known and hence these calls can be much
more efficient.

This is a much smaller API surface area from before and should greatly
simplify the `Func` documentation. There's still a problem where
`Func::wrapN_async` produces a lot of functions to document, but that's
now the sole offender. It's a nice benefit that the
statically-typed-async verisons are now expressed with an `async`
function rather than a function-returning-a-future which makes it both
more efficient and easier to understand.

The type `P` and `R` are intended to either be bare types (e.g. `i32`)
or tuples of any length (including 0). At this time `R` is only allowed
to be `()` or a bare `i32`-style type because multi-value is not
supported with a native ABI (yet). The `P`, however, can be any size of
tuples of parameters. This is also where some ergonomics are lost
because instead of `f(1, 2)` you now have to write `f.call((1, 2))`
(note the double-parens). Similarly `f()` becomes `f.call(())`.

Overall I feel that this is a better tradeoff than before. While not
universally better due to the loss in ergonomics I feel that this design
is much more flexible in terms of what you can do with the return value
and also understanding the API surface area (just less to take in).

[old-docs]: https://docs.rs/wasmtime/0.24.0/wasmtime/struct.Func.html#method.get0

* Rename Typed to TypedFunc

* Implement multi-value returns through `Func::typed`

* Fix examples in docs

* Fix some more errors

* More test fixes

* Rebasing and adding `get_typed_func`

* Updating tests

* Fix typo

* More doc tweaks

* Tweak visibility on `Func::invoke`

* Fix tests again
2021-03-11 14:43:34 -06:00

5.9 KiB

Using WebAssembly from Rust

This document shows an example of how to embed Wasmtime using the Rust API to execute a simple wasm program. Be sure to also check out the full API documentation for a full listing of what the wasmtime crate has to offer and the book examples for Rust for more information.

Creating the WebAssembly to execute

Creation of a WebAssembly file is generally covered by the Writing WebAssembly chapter, so we'll just assume that you've already got a wasm file on hand for the rest of this tutorial. To make things simple we'll also just assume you've got a hello.wat file which looks like this:

(module
  (func (export "answer") (result i32)
     i32.const 42
  )
)

Here we're just exporting one function which returns an integer that we'll read from Rust.

Hello, World!

First up let's create a rust project

$ cargo new --bin wasmtime_hello
$ cd wasmtime_hello

Next you'll want to add hello.wat to the root of your project.

We will be using the wasmtime crate to run the wasm file, so next up we need a dependency in Cargo.toml:

[dependencies]
wasmtime = "0.18.0"

Next up let's write the code that we need to execute this wasm file. The simplest version of this looks like so:

# extern crate wasmtime;
use std::error::Error;
use wasmtime::*;

fn main() -> Result<(), Box<dyn Error>> {
    let engine = Engine::default();
    // A `Store` is a sort of "global object" in a sense, but for now it suffices
    // to say that it's generally passed to most constructors.
    let store = Store::new(&engine);

# if false {
    // We start off by creating a `Module` which represents a compiled form
    // of our input wasm module. In this case it'll be JIT-compiled after
    // we parse the text format.
    let module = Module::from_file(&engine, "hello.wat")?;
# }
# let module = Module::new(&engine, r#"(module (func (export "answer") (result i32) i32.const 42))"#)?;

    // After we have a compiled `Module` we can then instantiate it, creating
    // an `Instance` which we can actually poke at functions on.
    let instance = Instance::new(&store, &module, &[])?;

    // The `Instance` gives us access to various exported functions and items,
    // which we access here to pull out our `answer` exported function and
    // run it.
    let answer = instance.get_func("answer")
        .expect("`answer` was not an exported function");

    // There's a few ways we can call the `answer` `Func` value. The easiest
    // is to statically assert its signature with `typed` (in this case
    // asserting it takes no arguments and returns one i32) and then call it.
    let answer = answer.typed::<(), i32>()?;

    // And finally we can call our function! Note that the error propagation
    // with `?` is done to handle the case where the wasm function traps.
    let result = answer.call(())?;
    println!("Answer: {:?}", result);
    Ok(())
}

We can build and execute our example with cargo run. Note that by depending on wasmtime you're depending on a JIT compiler, so it may take a moment to build all of its dependencies:

$ cargo run
  Compiling ...
  ...
   Finished dev [unoptimized + debuginfo] target(s) in 42.32s
    Running `wasmtime_hello/target/debug/wasmtime_hello`
Answer: 42

and there we go! We've now executed our first WebAssembly in wasmtime and gotten the result back.

Importing Host Functionality

What we've just seen is a pretty small example of how to call a wasm function and take a look at the result. Most interesting wasm modules, however, are going to import some functions to do something a bit more interesting. For that you'll need to provide imported functions from Rust for wasm to call!

Let's take a look at a wasm module which imports a logging function as well as some simple arithmetic from the environment.

(module
  (import "" "log" (func $log (param i32)))
  (import "" "double" (func $double (param i32) (result i32)))
  (func (export "run")
    i32.const 0
    call $log
    i32.const 1
    call $log
    i32.const 2
    call $double
    call $log
  )
)

This wasm module will call our "log" import a few times and then also call the "double" import. We can compile and instantiate this module with code that looks like this:

# extern crate wasmtime;
use std::error::Error;
use wasmtime::*;

fn main() -> Result<(), Box<dyn Error>> {
    let engine = Engine::default();
    let store = Store::new(&engine);
# if false {
    let module = Module::from_file(&engine, "hello.wat")?;
# }
# let module = Module::new(&engine, r#"(module (import "" "log" (func $log (param i32))) (import "" "double" (func $double (param i32) (result i32))) (func (export "run") i32.const 0 call $log i32.const 1 call $log i32.const 2 call $double call $log))"#)?;

    // First we can create our `log` function, which will simply print out the
    // parameter it receives.
    let log = Func::wrap(&store, |param: i32| {
        println!("log: {}", param);
    });

    // Next we can create our double function which doubles the input it receives.
    let double = Func::wrap(&store, |param: i32| param * 2);

    // When instantiating the module we now need to provide the imports to the
    // instantiation process. This is the second slice argument, where each
    // entry in the slice must line up with the imports in the module.
    let instance = Instance::new(&store, &module, &[log.into(), double.into()])?;

    let run = instance.get_typed_func::<(), ()>("run")?;
    Ok(run.call(())?)
}

Note that there's a number of ways to define a Func, be sure to consult its documentation for other ways to create a host-defined function.