Serverless Functions using Rust and WebAssembly
Front Line (Web) Assembly
Background
- enable the capabilities of the provided FDKs
- support functions in a language for which there is no FDK
WebAssembly
- write code in a language other than JavaScript
- compile it to WebAssembly
- run it in the browser
Polyglot Functions: Node and WebAssembly
Rust
- Write our function code in Rust
- Create a WebAssembly package
- Publish the package to npmjs
- Create a Node.js function in the usual way
- Reference the WebAssembly package in package.json
- Call the methods in the package from our JavaScript code in the usual way
Building the Function
Setup
Create a WebAssembly package with wasm-pack
$ wasm-pack new rust-fn
Add serialisation support
If we want to be able to serialise and deserialise objects between Rust and JS then we need to modify the Cargo.toml file so that the [dependencies] section matches the example:
[package] | |
name = "rust-fn" | |
version = "0.1.0" | |
authors = ["Ewan Slater <ewan.slater@gmail.com>"] | |
edition = "2018" | |
[lib] | |
crate-type = ["cdylib", "rlib"] | |
[features] | |
default = ["console_error_panic_hook"] | |
[dependencies] | |
wasm-bindgen = { version = "0.2.63", features = ["serde-serialize"] } | |
serde = { version = "1.*", features = ["derive"] } | |
# The `console_eror_panic_hook` crate provides better debugging of panics by | |
# logging them with `console.error`. This is great for development, but requires | |
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for | |
# code size when deploying. | |
console_error_panic_hook = { version = "0.1.6", optional = true } | |
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size | |
# compared to the default allocator's ~10K. It is slower than the default | |
# allocator, however. | |
# | |
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. | |
wee_alloc = { version = "0.4.5", optional = true } | |
[dev-dependencies] | |
wasm-bindgen-test = "0.3.13" | |
[profile.release] | |
# Tell `rustc` to optimize for small code size. | |
opt-level = "s" |
Expose Rust Functions
Edit the /src/lib.rs file so that it matches the example:
mod utils; | |
use wasm_bindgen::prelude::*; | |
use serde::*; | |
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global | |
// allocator. | |
#[cfg(feature = "wee_alloc")] | |
#[global_allocator] | |
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; | |
#[wasm_bindgen] | |
extern { | |
#[wasm_bindgen (js_namespace = console)] | |
fn log(s: &str); | |
} | |
#[wasm_bindgen] | |
pub fn hello_string(s: &str) -> String { | |
format!("Hello, {}!", s) | |
} | |
#[wasm_bindgen] | |
pub fn greet() -> String { | |
String::from("Hello, rust-fn!") | |
} | |
#[derive(Deserialize)] | |
pub struct Name { | |
pub name: String | |
} | |
#[derive(Serialize)] | |
pub struct Message { | |
pub message: String, | |
} | |
#[wasm_bindgen] | |
pub fn greet_json() -> JsValue { | |
let msg_string = String::from("Hello, rust-fn!"); | |
let msg = Message { | |
message: msg_string | |
}; | |
JsValue::from_serde(&msg).unwrap() | |
} | |
#[wasm_bindgen] | |
pub fn hello_json(i: &JsValue) -> JsValue { | |
let i_name: Name = i.into_serde() | |
.unwrap_or_else( |_e| { | |
Name { name: "World".to_string() } | |
}); | |
let msg = Message { | |
message: format!("Hello, {}!", i_name.name) | |
}; | |
JsValue::from_serde(&msg).unwrap() | |
} |
The key here is the annotations
The main one to consider is:
#[wasm_bindgen]
Functions with this annotation will be able to be called from JavaScript.
The other important annotations are:
#[derive(Serialize)]
#[derive(Deserialize)]
These declare the associated type to be serialisable or deserialisable.
There are two styles of function in this example. The simplest use strings to pass data to and from JavaScript, and the others (with the suffix _json) use JSON and serialization
Compile the Package
wasm-pack build --target nodejs --scope <your-node-js-user-id>
npm publish --access=public
Creating functions
fn init --runtime node <function-name>
{ | |
"name": "hellofn", | |
"version": "1.0.0", | |
"description": "example function", | |
"main": "func.js", | |
"author": "", | |
"license": "Apache-2.0", | |
"dependencies": { | |
"@fnproject/fdk": ">=0.0.18", | |
"@crush-157/rust-fn": ">=0.1.0" | |
} | |
} |
Sample Functions
lazy-rust
const fdk=require('@fnproject/fdk'); | |
const wasm=require('@crush-157/rust-fn'); | |
fdk.handle(function(input){ | |
let name = 'World'; | |
if (input.name) { | |
name = input.name; | |
} | |
return {'message': wasm.hello_string(name) } | |
}) |
busy-rust
const fdk=require('@fnproject/fdk'); | |
const wasm=require('@crush-157/rust-fn'); | |
fdk.handle(function(input){ | |
return wasm.hello_json(input); | |
}) |
Comments
Post a Comment