Web Assembly
Join us at HeatSync Labs for a tour of web assembly from Rust. Last months web talks left more questions than answers so we dig deeper into Wasm.
Come help us knock out some topics like:
- Exporting Rust crates to NPM
- Serverless with Cloudflare and Amazon
- A continuation of our frontend web talk
- The future of wasm outside of the browsers as scripting languages, on desktops, and even microcontrollers
Tour of Wasm ecosystem via @jacobrosenthal
- tight event loop, games, fluid interfaces
- math transformations (not offerd native by the os or browser, like ml commonly is now) audio, video. Basically before you had to hope your browser vendor shipped an api for mathy stuff, now you can build your own and ship it yourself.
- sandboxed, safe to deal with user input or extend your existing environment for game scripting, plugins, etc
Most basic example
first thing make sure rust is installed
Well need a one more tool to start, the 'target' were actually cross compiling here rustup target add wasm32-unknown-unknown
Then crate a new library we can link with cargo new --lib add1
cd add1
Next, we need to specify a special kind of library by adding this to this to the Cargo.toml
[lib]
crate-type = ["cdylib"]
Then add this to our lib.rs a simple function that we can call from javascript
#[no_mangle]
pub extern "C" fn add_one(x: i32) -> i32 {
x+1
}
cargo build --release --target=wasm32-unknown-unknown
and now weve got a wasm file at target/wasm32-unknown-unknown/release/add1.wasm
Lets make some terrible index.html
<!DOCTYPE html> <html> <head> <script> fetch("./target/wasm32-unknown-unknown/release/add1.wasm") .then((response) => response.arrayBuffer()) .then((bytes) => WebAssembly.instantiate(bytes)) .then((results) => { instance = results.instance; document.getElementById("demo").innerText = instance.exports.add_one(41); }) .catch(console.error); </script> </head> <body> <h1>Wasm Demo</h1> <p>1 + 41 equals</p> <p id="demo"></p> </body> </html>
and run a server to view it python -m SimpleHTTPServer 8000
Digging deeper for the nerds
so lets get complicated for a minute, feel free to ignore this,just to get your familiar
some tools IM going to use you dont need these I just want to show you under the hood
- https://github.com/WebAssembly/wabt
brew install wabt
- https://github.com/WebAssembly/binaryen
brew install binaryen
The wasm language actually instantiated in a .wat file, and you can write it yourself, though you really shouldnt. The idea is you could write in any language, and then 'target' wasm(wat) which would then be compiled to .wasm and shipped around for delivery, the same way that typescript wrote some higher level language (js plus types) and the compiled down to regular javascript
Lets try
touch add1.wat
and all all this to it
(module
(func $add_one (param $lhs i32) (result i32)
local.get $lhs
i32.const 1
i32.add)
(export "add_one" (func $add_one))
)
wat2wasm add1.wat -o add1.wasm
We can look inside our wasm file to see a ton of stuff including what functions it exports
wasm-objdump -x target/wasm32-unknown-unknown/release/add1.wasm
...
Custom:
- name: "name"
- func[0] <__wasm_call_ctors>
- func[1] <add_one>
...
Now edit our html file and pull this wasm file instead of our rust generated one and Run our server again python -m SimpleHTTPServer 8000
So we can see how we can hand craft wasm, or we can more commonly use a language like rust to target it.
Rust to npm workflow
We could start hooking more stuff up to our bare skeleton from before, but theres a template tool called wasm-pack thatll get us further faster
cargo install wasm-pack
npm init rust-webpack my-app
cd my-app
Note this is just your normal npm and webpack skelton, but with a crate stuck in the mix. Take a look at src/lib.rs theres a bit more boilerplate
and we have a package.json and a build and start alias which will call wasm-pack for you with, so we can just call the familiar npm run start
and when the browser opens well find our hello world in the console.
Note its bringing in a new project web-sys autogenerated bindings to all the web api. Almost everything gated by features, need to turn stuff on to get small code size
Theres also js-sys autogenerated JavaScript APIs that are guaranteed to exist in all standards-compliant ECMAScript environments, such as Array, Date, and eval. This is only if you need to generate one of these structures to pass back to a javascript function of some kind
Maybe youd like to import a function from your existing js code?
There are more examples which dig way deeper at the wasm-bindgen book
Digressions
debugging
native
Just like node packaged the v8 runtime so you can run your js code on your laptop and server, wasm is doing the same with the wasi standard. wasi is wasm but with libc syscalls mapped to your native environment, so it can get access to do things like read input and output, spawn threads, networking etc. So you can see a future where instead of say snap packages or installers with well ship wasm applications with excellent cross platform support.
Lets try the wasmtime interpreter.
cargo install wasmtime
rustup target add wasm32-wasi
cargo new helloworld
cd helloworld
notice this is a binary not a library, and notice no annotations or anything like before!
Lets add something that uses a syscall to main.rs to read text from the command line and write it back out
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
let mut iterator = stdin.lock().lines();
println!("type your name");
let name = iterator.next().unwrap().unwrap();
println!("hi {}!", name);
}
cargo build --target wasm32-wasi
And run it with the local cli interpreter
wasmtime target/wasm32-wasi/debug/helloworld.wasm
Now you could ship this same .wasm to another laptop that has wasmtime installed but is a different architecture or os and it will run there too. It could run on another desktop, mac, linux, windows. Eventually it will work on raspberry pi and arm devices and mobile, but much of that isnt ready yet sadly.
Eventually these wasi things will run on the web without javascript annotations we did before. In fact theres a js polyfill but its just proof of concept and doesnt support std::io or anything interesting atm. Keep an eye on it.
Wasm as Container Technology
BUT WAIT THERS MORE The security capabilites opt in and sandbox means its far more modern take than other interpreted byte code languages. This makes it more like Docker than node.
"If WASM+WASI existed in 2008, we wouldn't have needed to created Docker. That's how important it is. Webassembly on the server is the future of computing. A standardized system interface was the missing link. Let's hope WASI is up to the task!" - Cofounder of Docker
To that end, theres starting to be research on running in along side your os instead of on top, ring0 in projects like wasm-kernel
Wasm for constrained devices
BUT WAIT Why stop at computers with tons of ram. micropython or jerryscript are running byte code of high level languages on constrained devices. Yes. wasm is the future here too. Sadly its a C project not Rust, but theres an interpreter for constrained devices like microcontrollers from Intel
Wasm as plugin scripting
BUT WAIT THERES MORE Scripting languages in games, photoshop plugins, etc already do a lot of scripting. Youve probably been excited to find out some program or game allows you to script it but then found out its only in Python or Lua. This space is for wasm too. That way you can have scripting in your app but support whaever langauge the likes, and get better sandboxing and security for free.
BUT WAIT It will be embedded in even more lower level stuff. postgres is talking about using wasm to ship some peice of code you want to run your results against right on the server.
Its designed to be THE common target, thats especially good at user input because of the sandboxing
Conclusion
Think of wasm like any other interpreted language, ruby, javascript
We could ship the ascii text human readable script, like a .js across the web and it will run anywhere because you already previously downloaded a native interpreter for your systems (Chrome or node or ruby in this case). The interpreter will tokenize the ascii line by line and compile it and run it
And modern interpreters like v8 for javascript will also in the backgroup compile it to byte code, so it doesnt have to recompile it, but that takes time so they just start the interpreter and swap them out when its ready and you never notice the different..
Thats wasm, except lets have a singular language all the other languages can target so you can bring your favorite programming langauge with you and embed the result from websites to native to serverless to games and plugins to microcontrollers.
Resources:
- https://wasmbyexample.dev
- https://rustwasm.github.io/book/
- Programming WebAssembly with Rust https://pragprog.com/book/khrust/programming-webassembly-with-rust
- Main book that links to the wasm bindgen and wasm pack books https://rustwasm.github.io/docs/book/introduction.html
Wasm for serverless via @monteslu
Walkthrough and demo of the Wrangler tool from Cloudflare
- https://blog.cloudflare.com/introducing-wrangler-cli/
- https://developers.cloudflare.com/workers/templates/boilerplates/rustwasm/