Trait GetHost

Source
pub trait GetHost<T>:
    Fn(T) -> Self::Host
    + Send
    + Sync
    + Copy
    + 'static {
    type Host;
}
Available on crate features runtime and component-model only.
Expand description

An “alias trait” used for bindgen!-generated add_to_linker_get_host-style functions.

This trait is used as a trait bound for closures that “project” from a store’s &mut T internals to U where U is allowed to close over the lifetime of &mut T. This is a somewhat tricky operation to write down in a trait bound which requires a somewhat special definition of this trait.

This trait has a supertrait of Fn(T) -> U where U here is specified as an associated type Host for this trait. There is then additionally a blanket implementation for all Fn(T) -> U closures as well meaning no embedders should need to implement this trait manually.

Note that this trait additionally requires that closures are Send + Sync + Copy + 'static which means they effectively need to behave like function pointers.

Also note that using this trait requires type-annotations at this time. Refer to the example below for more information.

§Examples

Embedders should generally not need to write GetHost bounds themselves but they will need to interact with functions that have a parameter bounded by GetHost generated by bindgen!.

use wasmtime::component::{Linker, GetHost};

// generated by `bindgen!`
trait Host { /* ... */ }
// generated by `bindgen!`
impl<'a, T: ?Sized + Host> Host for &'a mut T { /* ... */ }

// generated by `bindgen!`
fn add_to_linker_get_host<T, G>(linker: &mut Linker<T>, get_host: G)
    where G: for<'a> GetHost<&'a mut T, Host: Host>,
{
    // ...
}

// In some cases you'll implement `Host` for the `T` in the store directly.
struct MyType;

impl Host for MyType {
    // ...
}

fn my_add_to_linker(linker: &mut Linker<MyType>) {
    let get_host = type_annotate(|x| x);
    add_to_linker_get_host(linker, get_host);

    // Type annotating a closure is not the most intuitive thing to do in
    // Rust, but that's all this function is doing. This is taking a `F`
    // and then simply returning it, but the purpose of this function is to
    // synthetically place a bound on `F` here with a `where` clause. This
    // `where` clause is what annotates the type of `F` manually and is how
    // we accurately describe the lifetimes associated with this closure.
    fn type_annotate<F>(f: F) -> F
        where F: Fn(&mut MyType) -> &mut MyType
    {
        f
    }
}

// In other cases you might store the structure implementing `Host`
// elsewhere in the store
struct MyContainerType {
    the_impl: MyType,
}

fn my_add_to_linker_with_container(linker: &mut Linker<MyContainerType>) {
    let get_host = type_annotate(|x| &mut x.the_impl);
    add_to_linker_get_host(linker, get_host);

    fn type_annotate<F>(f: F) -> F
        where F: Fn(&mut MyContainerType) -> &mut MyType
    {
        f
    }
}

// And in some cases you might want to combine pieces of state within a `T`
// of a `Store` into one type that implements this state. This is, for
// example, how WASI impls work in Wasmtime.
struct MyStoreState {
    the_table: MyTable,
    the_impl: MyType,
}

struct MyTable { /* ... */ }

struct MyBorrowedState<'a> {
    the_table: &'a mut MyTable,
    the_impl: &'a mut MyType,
}

impl Host for MyBorrowedState<'_> {
    // ...
}

fn my_add_to_linker_with_more_lifetimes(linker: &mut Linker<MyStoreState>) {
    let get_host = type_annotate(|x| MyBorrowedState {
        the_table: &mut x.the_table,
        the_impl: &mut x.the_impl,
    });
    add_to_linker_get_host(linker, get_host);

    fn type_annotate<F>(f: F) -> F
        where F: Fn(&mut MyStoreState) -> MyBorrowedState<'_>
    {
        f
    }
}

§Too Much Detail

The general idea behind this trait is that it expresses the ability to take a closure as a parameter which projects from a store’s state, T, to a U which implements the bindgen-generated traits. The actual operation is provided a &'a mut T at runtime and U must then also be able to close over the 'a too.

This is surprisingly subtle to do in Rust and there are many variations of writing this down which don’t achieve the desired result. For example the most naive way to possibly write this does not work.

use wasmtime::component::Linker;

// Naively let's just take `F: FnMut...`
fn add_to_linker_get_host<F, T, U>(linker: &mut Linker<T>, get_host: F)
    where F: FnMut(&mut T) -> U,
{
    // ...
}

struct MyStoreType { the_impl: MyType, }
struct MyType { /* ... */ }
impl Host for MyType { /* ... */ }

fn my_add_to_linker(linker: &mut Linker<MyType>) {
    let get_host = type_annotate(|x| x);
    add_to_linker_get_host(linker, get_host);
    //~^ ERROR: one type is more general than the other

    fn type_annotate<F>(f: F) -> F
        where F: Fn(&mut MyType) -> &mut MyType
    {
        f
    }
}

The problem with this version is that the exact definition of the trait bound is: where F: for<'a> FnMut(&'a mut T) -> U, and U doesn’t have access to the lifetime 'a that’s only defined under the trait bound of F itself. So this doesn’t work but how about lifting the lifetime up?

use wasmtime::component::Linker;

fn add_to_linker_get_host<'a, F, T: 'a, U>(linker: &mut Linker<T>, get_host: F)
    where F: FnMut(&'a mut T) -> U + Send + Sync + 'static,
{
    linker.root().func_wrap("f", move |mut store, ()| {
        let mut data = store.data_mut();
        let the_u = get_host(&mut data);
        //~^ ERROR: borrowed data escapes outside of closure
        Ok(())
    });
}

The problem with this invocation is the meaning is still wrong. While the above example will compile with 'a defined here the actual usage of get_host doesn’t work. The root of the issue is that the lifetime can’t actually be named when add_to_linker_get_host is called, instead it must be part of the for<'a> part.

Rust doesn’t have higher-ranked definitions such as for<'a, U> Fn(&'a mut T) -> U where U would have access to the 'a lifetime here, meaning that at this point we’ve exhausted the various possibilities of using Fn trait bounds directly. They all mean subtly different things than the original problem we’re trying to solve here.

This trait definition exists to accurately express in the trait system what we’re trying to solve which is that the return value of a closure is allowed to close over its arguments. This is achieved where the trait here is defined with just T, notably not &'a mut T, meaning that whatever is in scope for T is also in-scope for the returned value from the closure. Usage of this trait then looks like G: for<'a> GetHost<&'a mut T> meaning that the G::Host associated type has access to 'a.

This then gives rise to the second issue of this trait which is actually defining a closure that implements this trait. Ideally what we want to write is something that looks like this:

let my_closure = for<'a> |x: &'a mut MyStoreType| -> &'a mut MyType {
    &mut x.the_impl
};

This syntax technically compiles but is unstable. Alternatively one might want to write:

let my_closure: impl FnMut(&mut MyStoreType) -> &mut MyType = |x| {
    &mut x.the_impl
};

but this syntax is also unstable. Thus what we’re left with is the sort of kludge workaround in the examples above:

let my_closure = type_annotate(|x| &mut x.the_impl);

fn type_annotate<F>(f: F) -> F
    where F: Fn(&mut MyStoreType) -> &mut MyType
{
    f
}

Our hope is that this can get more ergonomic over time, but this is hopefully at least illuminating how we have ended up here.

Required Associated Types§

Source

type Host

The type that this accessor returns. Notably this has access to the lifetimes of T.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§

Source§

impl<F, T, U> GetHost<T> for F
where F: Fn(T) -> U + Send + Sync + Copy + 'static,

Source§

type Host = U