Getting Started with Function as A Service in Rust

Sep 27, 2020 • 6 minutes to read

Function as a Service (FaaS) is one of the fastest growing areas of cloud computing. FaaS allows developers to focus on the code. Once the developer uploads the code, the FaaS takes care of deployment, service availability, and scalability. The developer only pays for resources the service uses, not reserved idle time. This approach, known as serverless computing, is the way to build inter-connected and microservice-based applications. The result are fast development turn around, easy deployment, high availability, infinite scalability, at low cost.

However, traditional FaaS are based on microVM (eg Firecracker and gVisor) and application container (eg Docker) technologies. They are general computing platforms not optimized for software stacks. To boot an entire OS and then heavy-weight runtimes just to run a single function is very inefficient. Therefore, existing FaaS solutions suffer from issues such as slow cold start, slow runtime performance, bloated runtime, and time-based billing. They are not suitable for computationally intensive applications.

High-level language VMs, such as WebAssembly, offer a combination of ease-of-use, runtime safety, and high performance. The WasmEdge Runtime is a WebAssembly VM that is designed for edge cloud and device applications. It is a great fit for computationally intensive FaaS applications such as edge AI, real-time data analytics, multimedia processing, as well as typical transactional functions that need to start in sub-millisecond and make a quick call to another web service.

The Second State FaaS is a service built on the WasmEdge Runtime. Currently, we provide a comprehensive toolchain to support functions written in Rust. More language support is coming. We selected Rust because of its superior performance, memory safety, and developer support — the traits our WebAssembly FaaS thrives to achieve. Indeed, Rust is voted as the most beloved programming language by StackOverflow users in the 5 years in a row.

In this series of tutorials, we will show you how to use the Second State FaaS to develop inter-connected functions that perform real world computing tasks. Let's start with a very simple hello world example!

The source code for the hello world example in this article is available on Github.

Prerequisites

Follow these simple instructions to install Rust and rustwasmc.

A Rusty hello world

Below is the entire content of the src/lib.rs file. It is a Rust function that takes a string argument and returns a string value. It prefixes “hello” to an input argument, and then returns the string back to the function’s caller.

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn say(s: &str) -> String {
  let r = String::from("hello ");
  return r + s;
}

Before compiling, make sure that your Cargo.toml file has declared the correct dependencies.

[dependencies]
wasm-bindgen = "=0.2.61"

Build and deploy

You need to use the rustwasmc tool to compile the Rust function into WebAssembly bytecode (i.e., the wasm file). Do the following and the compiled wasm file is in the pkg directory.

$ rustwasmc build

Upload the wasm file in the pkg folder to the FaaS /api/executables RPC service endpoint. Double check the .wasm file name before you upload.

$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/executables' \
--header 'Content-Type: application/octet-stream' \
--header 'SSVM-Description: say hello' \
--data-binary '@pkg/hello_lib_bg.wasm'

Once the wasm file is deployed, the FaaS returns a wasm_id for the deployment. You will use this wasm_id to access the functions in the deployed wasm file later.

{"wasm_id":161,"wasm_sha256":"0xfb413547a8aba56d0349603a7989e269f3846245e51804932b3e02bc0be4b665","usage_key":"00000000-0000-0000-0000-000000000000","admin_key":"00xxxxxx-xxxx-xxxx-xxxx-4adc960fd2b8"}

Invoke the FaaS function

You can now call the Rust function over the web. The RPC service endpoint is /api/run/wasm_id/function_name where the wasm_id is the ID for the wasm file we deployed, and the function_name is the name of the function we want to invoke in the wasm file.

The HTTP request body is passed to the function as the call argument. As we have seen, the function takes a string argument, and hence the HTTP body is converted into a text string and passed to the function. The HTTP response body is the return string value from the function.

$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/run/161/say' \
--header 'Content-Type: text/plain' \
--data-raw 'Second State FaaS'
hello Second State FaaS

Web UI

One of the most compelling use cases of FaaS is for the functions to act as backend services for static web sites. Below is the JavaScript snippet to make an AJAX call to this FaaS function.

Source code | Live demo

  $.ajax({
      url: "https://rpc.ssvm.secondstate.io:8081/api/run/161/say",
      type: "post",
      data : $('#input').val(),
      contentType: "text/plain",
      processData: false,
      success: function (data) {
        $('#result').html(data);
      }
  });

That’s it! You have successfully developed and deployed your first FaaS in Rust!

Use JSON to pass multiple arguments

As we have seen, the Rust function can take a string value as input argument. What if we want to pass complex data types? You can always encode them into a JSON string! In this example, we will demonstrate a function that extracts matches from a regular expression. The source code is available on Github. Below is the Rust function.

use wasm_bindgen::prelude::*;
use regex::Regex;

#[wasm_bindgen]
pub fn match_text (params: &str) -> String {
    let (regex, text) : (String, String) = serde_json::from_str(&params).unwrap();
    let mut vec : Vec<String> = Vec::new();
    for mat in Regex::new(&regex).unwrap().find_iter(&text) {
        // println!("{:?}", mat);
        vec.push(mat.as_str().to_string());
    }
    return serde_json::to_string(&vec).unwrap();
} 

The first line of the function uses the Rust serde_json crate (library) to parse the input argument from JSON into two strings. The return value is also a JSON object containing an array of strings. The Cargo.toml of this project shows dependencies on serde and regex crates.

[dependencies]
regex = "1.3.9"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wasm-bindgen = "=0.2.61"

After building with rustwasmc and deploying, below is how you call the function with an JSON HTTP request.

$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/run/162/match_text' \
--header 'Content-Type: text/plain' \
--data '["\\d{4}-\\d{2}-\\d{2}","On 2009-01-03, Satoshi Nakamoto launched the Bitcoin blockchain. The price reached a high of $19,783.06 on 2017-12-17 and dropped to a low of $3,300 on 2018-12-07."]'
["2009-01-03","2017-12-17","2018-12-07"]

On a static web page, you can use JavaScript to make an AJAX call to this FaaS function.

Source code | Live demo

  jsonObj = [];
  jsonObj.push($('#pattern').val());
  jsonObj.push($('#input').val());

  $.ajax({
      url: "https://rpc.ssvm.secondstate.io:8081/api/run/162/match_text",
      type: "post",
      data : JSON.stringify(jsonObj),
      contentType: "text/plain",
      processData: false,
      success: function (data) {
        $('#result').html(data);
      }
  });

What’s next

The input and output of our example are string values. They are passed in HTTP request and response bodies. You can pass arbitrarily complex data by encoding them into JSON strings. You can see examples on how to use JSON in Rust functions.

However, FaaS functions often need process binary data, such as images. It is inefficient to encode and decode binary data to and from JSON strings in every call interaction. In the next article, we will demonstrate how to create a function that directly deals with binary data.

RustJavaScriptWebAssemblyNode.jsFaaSRust FaaSServerlesscloud computing
A high-performance, extensible, and hardware optimized WebAssembly Virtual Machine for automotive, cloud, AI, and blockchain applications