How to Add WebAssembly Support to a General-Purpose Crate (original) (raw)

  1. 1. Introduction
  2. 2. Why Rust and WebAssembly?
  3. 3. Background And Concepts
    1. 3.1. What is WebAssembly?
  4. 4. Tutorial
    1. 4.1. Setup
    2. 4.2. Hello, World!
    3. 4.3. Rules
    4. 4.4. Implementing Life
    5. 4.5. Testing Life
    6. 4.6. Debugging
    7. 4.7. Adding Interactivity
    8. 4.8. Time Profiling
    9. 4.9. Shrinking .wasm Size
    10. 4.10. Publishing to npm
  5. 5. Reference
    1. 5.1. Crates You Should Know
    2. 5.2. Tools You Should Know
    3. 5.3. Project Templates
    4. 5.4. Debugging
    5. 5.5. Time Profiling
    6. 5.6. Shrinking .wasm Size
    7. 5.7. JavaScript Interoperation
    8. 5.8. Which Crates Will Work Off-the-Shelf with WebAssembly?
    9. 5.9. How to Add WebAssembly Support to a General-Purpose Crate
    10. 5.10. Deploying Rust and WebAssembly to Production

Rust and WebAssembly

How to Add WebAssembly Support to a General-Purpose Crate

This section is for general-purpose crate authors who want to support WebAssembly.

Maybe Your Crate Already Supports WebAssembly!

Review the information about what kinds of things can make a general-purpose crate not portable for WebAssembly. If your crate doesn't have any of those things, it likely already supports WebAssembly!

You can always check by running cargo build for the WebAssembly target:

cargo build --target wasm32-unknown-unknown

If that command fails, then your crate doesn't support WebAssembly right now. If it doesn't fail, then your crate might support WebAssembly. You can be 100% sure that it does (and continues to do so!) by adding tests for wasm and running those tests in CI.

Adding Support for WebAssembly Avoid Performing I/O Directly

On the Web, I/O is always asynchronous, and there isn't a file system. Factor I/O out of your library, let users perform the I/O and then pass the input slices to your library instead.

For example, refactor this:


# #![allow(unused_variables)]
#fn main() {
use std::fs;
use std::path::Path;

pub fn parse_thing(path: &Path) -> Result<MyThing, MyError> {
    let contents = fs::read(path)?;
    // ...
}
#}

Into this:


# #![allow(unused_variables)]
#fn main() {
pub fn parse_thing(contents: &[u8]) -> Result<MyThing, MyError> {
    // ...
}
#}

Add wasm-bindgen as a Dependency

If you need to interact with the outside world (i.e. you can't have library consumers drive that interaction for you) then you'll need to add wasm-bindgen(and js-sys and web-sys if you need them) as a dependency for when compilation is targeting WebAssembly:

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = "0.3"

Avoid Synchronous I/O

If you must perform I/O in your library, then it cannot be synchronous. There is only asynchronous I/O on the Web. Use the futurescrate and the wasm-bindgen-futurescrate to manage asynchronous I/O. If your library functions are generic over some future type F, then that future can be implemented via fetch on the Web or via non-blocking I/O provided by the operating system.


# #![allow(unused_variables)]
#fn main() {
pub fn do_stuff<F>(future: F) -> impl Future<Item = MyOtherThing>
where
    F: Future<Item = MyThing>,
{
    // ...
}
#}

You can also define a trait and implement it for WebAssembly and the Web and also for native targets:


# #![allow(unused_variables)]
#fn main() {
trait ReadMyThing {
    type F: Future<Item = MyThing>;
    fn read(&self) -> Self::F;
}

#[cfg(target_arch = "wasm32")]
struct WebReadMyThing {
    // ...
}

#[cfg(target_arch = "wasm32")]
impl ReadMyThing for WebReadMyThing {
    // ...
}

#[cfg(not(target_arch = "wasm32"))]
struct NativeReadMyThing {
    // ...
}

#[cfg(not(target_arch = "wasm32"))]
impl ReadMyThing for NativeReadMyThing {
    // ...
}
#}

Avoid Spawning Threads

Wasm doesn't support threads yet (but experimental work is ongoing), so attempts to spawn threads in wasm will panic.

You can use #[cfg(..)]s to enable threaded and non-threaded code paths depending on if the target is WebAssembly or not:


# #![allow(unused_variables)]
#![cfg(target_arch = "wasm32")]
#fn main() {
fn do_work() {
    // Do work with only this thread...
}

#![cfg(not(target_arch = "wasm32"))]
fn do_work() {
    use std::thread;

    // Spread work to helper threads....
    thread::spawn(|| {
        // ...
    });
}
#}

Another option is to factor out thread spawning from your library and allow users to "bring their own threads" similar to factoring out file I/O and allowing users to bring their own I/O. This has the side effect of playing nice with applications that want to own their own custom thread pool.

Maintaining Ongoing Support for WebAssembly Building for wasm32-unknown-unknown in CI

Ensure that compilation doesn't fail when targeting WebAssembly by having your CI script run these commands:

rustup target add wasm32-unknown-unknown
cargo check --target wasm32-unknown-unknown

For example, you can add this to your .travis.yml configuration for Travis CI:


matrix:
  include:
    - language: rust
      rust: stable
      name: "check wasm32 support"
      install: rustup target add wasm32-unknown-unknown
      script: cargo check --target wasm32-unknown-unknown

Testing in Node.js and Headless Browsers

You can use wasm-bindgen-test and the wasm-pack test subcommand to run wasm tests in either Node.js or a headless browser. You can even integrate these tests into your CI.

Learn more about testing wasm here.