Like most successful duos, Rust and WebAssembly (Wasm) complement each other. Rust is a typesafe systems language with modern tooling and high-level features. WebAssembly is a portable compilation target/execution environment for the Web browser and beyond. The combination makes it possible to write fast, stable software that runs anywhere without recompilation.

But there’s a catch. Wasm only supports numeric types as function arguments, and documentation describing how to transfer strings is scarce. A vast network of tools such as wasm-bindgen has sprung up to make two-way data sharing more convenient. An advantage in some situations, these tools complicate understanding what’s going on behind the scenes or if such abstraction is even necessary.

This article takes a different approach with a simple Hello World written in Rust that compiles to WebAssembly using minimal tooling. Custom JavaScript executes the compiled Rust. The complete project can be downloaded from GitHub. For the first article in this series, see Compiling Rust to WebAssembly: A Simple Example. That article contains some background information not re-iterated here.

Prerequisites

Begin by installing the Rust language tools with rustup if you haven’t already. Next, install the Wasm toolchain:

rustup target add wasm32-unknown-unknown

Finally, create a test project and change into it:

cargo new hello --lib

cd hello

Update Cargo.toml

The file Cargo.toml (aka “manifest”) contains the project’s configuration. Leave everything, but append a new block called [lib]. The result should look something this:

# Cargo.toml
[package]
name = "hello"
version = "0.1.0"
authors = ["Richard Apodaca <rich.apodaca@gmail.com>"]
edition = "2018"

[dependencies]

# new material
[lib]
crate-type = ["cdylib"]

A Little About Linear Memory

JavaScript and WebAssembly work together using a feature called linear memory. On the JavaScript side, linear memory is usually represented as a regular typed array such as Uint8Array. On the WebAssembly/Rust side, linear memory looks like regular memory. Both sides can write to linear memory, but they do so in different ways.

The Rust Side

From Rust we export three functions:

  • alloc allocates a block of linear memory, returning a pointer to it
  • dealloc deallocates a previously-allocated block of linear memory given a pointer to it
  • greet uses a pointer passed to it to read <name> from linear memory, and write the greeting “Hello, <name>!”.

Here’s the complete listing:

// lib.rs
use std::mem;
use std::ffi::{CString, CStr};
use std::os::raw::c_void;

#[no_mangle]
pub extern "C" fn alloc() -> *mut c_void {
    let mut buf = Vec::with_capacity(1024);
    let ptr = buf.as_mut_ptr();

    mem::forget(buf);

    ptr
}

#[no_mangle]
pub unsafe extern "C" fn dealloc(ptr: *mut c_void) {
    let _ = Vec::from_raw_parts(ptr, 0, 1024);
}

#[no_mangle]
pub unsafe extern "C" fn greet(ptr: *mut u8) {
    let str_content = CStr::from_ptr(ptr as *const i8).to_str().unwrap();
    let mut string_content = String::from("Hello, ");

    string_content.push_str(str_content);
    string_content.push_str("!");

    let c_headers = CString::new(string_content).unwrap();

    let bytes = c_headers.as_bytes_with_nul();

    let header_bytes = std::slice::from_raw_parts_mut(ptr, 1024);
    header_bytes[..bytes.len()].copy_from_slice(bytes);
}

Nothing in the Rust code couples it to the environment in which it will run. All that’s needed is the FFI module built into the standard library. As such, the same code could be used to create C or Python bindings. Coupling to WebAssembly occurs at compile time.

#rust #webassembly #developer

Rust and WebAssembly from Scratch: Hello World with Strings
1.90 GEEK