Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Using Library

Add this to your Crate.toml:

[dependencies]
externref = "0.3.0-beta.1"

See the library docs for detailed description of its API.

General workflow

The basic approach is as follows:

  1. Use Resources as arguments / return results for imported and/or exported functions in a WASM module in place of externrefs . Reference args (including mutable references) and the Option<_> wrapper are supported as well.
  2. Add the #[externref] proc macro on the imported / exported functions.
  3. Post-process the generated WASM module with the processor. This can be possible

Resources support primitive downcasting and upcasting with Resource<()> signalling a generic resource. Downcasting is unchecked; it is up to the Resource users to define a way to check the resource kind dynamically if necessary. One possible approach for this is defining a WASM import fn(&Resource<()>) -> Kind, where Kind is the encoded kind of the supplied resource, such as i32.

Note

Resource is essentially a smart pointer. Correspondingly, its Eq and Hash trait implementations treat it as such (i.e., two resources are equal if they point to the same externref).

Note

Resource implements the RAII pattern, in which it releases the associated externref on drop. Correspondingly, Resource does not implement Clone / Copy. To clone resources, you may want to wrap it in a Rc / Arc, or to use copyable resources.

Basic example

The code sample below demonstrates the basic usage of the #[externref] proc macro with 2 resource types.

#![allow(unused)]
fn main() {
use externref::{externref, Resource};

// Two marker types for different resources.
pub struct Arena(());
pub struct Bytes(());

#[cfg(target_arch = "wasm32")]
#[externref]
#[link(wasm_import_module = "arena")]
extern "C" {
    // This import will have signature `(externref, i32) -> externref`
    // on host.
    fn alloc(arena: &Resource<Arena>, size: usize) 
        -> Option<Resource<Bytes>>;
}

// Fallback for non-WASM targets.
#[cfg(not(target_arch = "wasm32"))]
unsafe fn alloc(_: &Resource<Arena>, _: usize) 
    -> Option<Resource<Bytes>> { None }

// This export will have signature `(externref) -> ()` on host.
#[externref]
#[unsafe(export_name = "test_export")]
pub extern "C" fn test_export(arena: &Resource<Arena>) {
    let bytes = unsafe { alloc(arena, 42) }.expect("cannot allocate");
    // Do something with `bytes`...
}
}

Copyable resources

ResourceCopy is a variation of Resource that implements Clone / Copy. As a trade-off, it does not implement any resource management.

Imports below allow to construct a copyable resource (ResourceCopy<Bytes>) from an ordinary one.

#![allow(unused)]
fn main() {
type MessageCopy = ResourceCopy<Bytes>;

#[externref::externref]
#[link(wasm_import_module = "test")]
unsafe extern "C" {
    #[resource]
    pub(crate) fn send_message_copy(
        sender: &Resource<Sender>,
        message_ptr: *const u8,
        message_len: usize,
    ) -> MessageCopy;

    /// This is valid because `Resource<..>` is guaranteed to have `usize` representation,
    /// so the host essentially receives the index into the `externrefs` table.
    pub(crate) fn inspect_message(#[resource = false] bytes: MessageCopy);
}
}

Then, these resources can be used in the app similarly to shared references (&_).

#![allow(unused)]
fn main() {
#[externref]
pub extern "C" fn test_export_with_copies(sender: Resource<Sender>) {
    let str = "test";
    let message = unsafe { imports::send_message_copy(&sender, str.as_ptr(), str.len()) };
    let other_message = unsafe { imports::send_message(&sender, str.as_ptr(), str.len()) };
    // `ResourceCopy` can be created by leaking a `Resource<_>`.
    let other_message = other_message.leak();
    let message_copy = message;

    // `Resource`s implement pointer comparison semantics.
    assert!(message == message);
    assert!(message_copy == message);
    assert!(message != other_message);

    let messages: HashSet<_> =
        [message, other_message, message, message_copy, other_message].into();
    assert_eq!(messages.len(), 2);
    for message in messages {
        unsafe {
            imports::inspect_message(message);
        }
    }
}
}

Important

For some apps, it’s acceptable for the externref table to grow unboundedly (e.g., if the target module is executed for a short duration and then the execution env is discarded). In other cases, the table cleanup must be implemented by the caller, e.g. by hooking to the Drop implementation for the ResourceCopy wrapper, which may pass ResourceCopy (i.e., the index in the externref table) to the host for cleanup.