Rust Functions
Write, build, and deploy Rust functions on Fission, compiled by the builder into native server binaries for native-speed invocations with no per-request process startup.
The runtime is built on axum and tokio. This guide covers setting up the environment, writing functions, handling HTTP requests/responses, and working with dependencies.
Before you start
We’ll assume you have Fission and Kubernetes set up. If not, head over to the install guide. Verify your Fission setup with:
fission version
Add the Rust environment to your cluster
Rust is a compiled language, so source code must be compiled before it runs. The builder manager inside Fission does this automatically whenever a Rust function or package is created: the Rust builder converts a source package into a deployable native binary.
fission environment create --name rust \
--image ghcr.io/fission/rust-env \
--builder ghcr.io/fission/rust-builder
Rust environment image list
| Image | Builder Image |
|---|---|
| ghcr.io/fission/rust-env | ghcr.io/fission/rust-builder |
Write a simple function in Rust
A single-file function is one .rs file that defines pub async fn handler.
Any axum handler signature works, so you can use extractors, Json, headers, and so on.
Here is a hello world example (hello.rs):
use fission_rust::IntoResponse;
pub async fn handler() -> impl IntoResponse {
"Hello, World!\n"
}
Create and test the function:
fission fn create --name helloworld --env rust --src hello.rs
Before accessing the function, make sure its package build has succeeded:
fission pkg info --name <pkg-name>
Now test it:
$ fission fn test --name helloworld
Hello, World!
See here for how to set up different triggers for a Rust function.
Single-file functions may use the crates pre-baked into the builder: fission-rust, axum, tokio, serde, and serde_json.
For other dependencies, use a Cargo project.
HTTP requests and HTTP responses
The function receives every request routed to it; axum extractors give you typed access to all parts of the request.
Accessing HTTP Requests
Headers
Read a request header with an axum extractor:
use fission_rust::IntoResponse;
use fission_rust::axum::http::HeaderMap;
pub async fn handler(headers: HeaderMap) -> impl IntoResponse {
let v = headers
.get("x-my-header")
.and_then(|v| v.to_str().ok())
.unwrap_or("not set");
format!("x-my-header: {v}\n")
}
Create an HTTP trigger and call it:
fission httptrigger create --method GET --url "/<url>" --function <fn-name>
$ curl http://$FISSION_ROUTER/<url> -H 'X-My-Header: foo'
x-my-header: foo
Query string
Read query-string parameters into a map:
use std::collections::HashMap;
use fission_rust::IntoResponse;
use fission_rust::axum::extract::Query;
pub async fn handler(Query(params): Query<HashMap<String, String>>) -> impl IntoResponse {
params.get("key-name").cloned().unwrap_or_default()
}
$ curl "http://$FISSION_ROUTER/<url>?key-name=123"
123
Request Body
Plain text
Read the raw request body as a string:
use fission_rust::IntoResponse;
pub async fn handler(body: String) -> impl IntoResponse {
body
}
$ curl -X POST http://$FISSION_ROUTER/<url> -d foobar
foobar
JSON
Deserialize a JSON request body into a struct:
use fission_rust::IntoResponse;
use fission_rust::axum::Json;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
pub struct Msg {
content: String,
}
pub async fn handler(Json(msg): Json<Msg>) -> impl IntoResponse {
Json(msg)
}
$ curl -X POST http://$FISSION_ROUTER/<url> \
-H 'Content-Type: application/json' -d '{"content": "foobar"}'
{"content":"foobar"}
Controlling HTTP Responses
Setting response headers and status codes
Return a tuple of status, headers, and body — axum converts it into a response:
use fission_rust::IntoResponse;
use fission_rust::axum::http::StatusCode;
pub async fn handler() -> impl IntoResponse {
(
StatusCode::CREATED,
[("x-request-handled-by", "fission-rust")],
"created\n",
)
}
$ curl -i http://$FISSION_ROUTER/<url>
HTTP/1.1 201 Created
x-request-handled-by: fission-rust
...
Handler panics are caught by the runtime: the request returns HTTP 500 and the function keeps serving.
Multiple module files
A source archive may contain a handler.rs plus extra module files.
Each file becomes a crate-root module, so handler.rs can reach a sibling util.rs as crate::util:
// util.rs
pub fn greeting() -> String {
"Hello from a sibling module!\n".to_string()
}
// handler.rs
use fission_rust::IntoResponse;
pub async fn handler() -> impl IntoResponse {
crate::util::greeting()
}
zip -r function.zip handler.rs util.rs
fission pkg create --name multimod --env rust --src function.zip
Working with dependencies (Cargo projects)
For third-party crates, supply a full Cargo binary crate as the source package.
The only contract: the binary must serve HTTP on 127.0.0.1:$FISSION_RUNTIME_PORT.
The easiest way is the fission-rust SDK:
# Cargo.toml
[package]
name = "echo-example"
version = "0.1.0"
edition = "2024"
[dependencies]
fission-rust = { git = "https://github.com/fission/environments", rev = "<commit-sha>" }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
serde_json = "1"
Pin the dependency to a commit with rev for reproducible builds.
Without rev the git dependency tracks master and can change under you between builds; pinning a rev keeps every build resolving the same source.
The builder pod fetches the crate over the network at build time, so the builder needs egress to GitHub — the same as for any crates.io dependency.
// src/main.rs
use fission_rust::IntoResponse;
use fission_rust::axum::Json;
use serde_json::Value;
async fn handler(body: String) -> impl IntoResponse {
let value: Value = serde_json::from_str(&body)
.unwrap_or_else(|_| Value::String(body));
Json(value)
}
#[tokio::main]
async fn main() {
fission_rust::serve(handler).await;
}
Archive and create the package as usual:
$ zip -r echo.zip Cargo.toml src
$ fission pkg create --name echo --env rust --src echo.zip
$ fission fn create --name echo --env rust --pkg echo
Bring your own framework
Because the contract is just “serve HTTP on $FISSION_RUNTIME_PORT”, any Rust web framework works — actix-web, rocket, warp, or raw hyper:
// src/main.rs — plain axum without the SDK
use axum::{Router, routing::get};
#[tokio::main]
async fn main() {
let port: u16 = std::env::var("FISSION_RUNTIME_PORT")
.ok().and_then(|p| p.parse().ok()).unwrap_or(8889);
let app = Router::new().route("/", get(|| async { "Hello!\n" }));
let listener = tokio::net::TcpListener::bind(("127.0.0.1", port)).await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Multiple binaries and entrypoints
If the project defines several [[bin]] targets, the builder deploys all of them and the function’s --entrypoint selects one by name:
fission fn create --name myfn --env rust --pkg mypkg --entrypoint <binary-name>
A project with a single binary needs no entrypoint — it is always deployed under the default name handler.
How it works
The runtime image runs a small supervisor that implements the Fission environment interface. At specialization it starts your compiled function binary once and then reverse-proxies all requests to it over a pooled local connection, so steady-state overhead is a single localhost hop. If the function process exits, the pod is replaced automatically.
Modifying/Rebuilding the environment images
Refer to the Rust environment guide to learn about rebuilding the environment images.
Resource usage
Like any function, you can bound a Rust function’s resources:
fission fn create --name echo --env rust --pkg echo \
--mincpu 20 --maxcpu 100 --minmemory 64 --maxmemory 128
Compiled Rust functions are typically small (a hello world binary is around 1 MB) and have low memory floors, so they are a good fit for tight resource limits.
Use kubectl top pod -l functionName=<name> while benchmarking to find the right values.