diff --git a/content/en/_index.html b/content/en/_index.html index 12f97206..b6f3b24d 100644 --- a/content/en/_index.html +++ b/content/en/_index.html @@ -121,8 +121,8 @@

Fission is extensible to any programming language (Python, NodeJS, - Go, C#, PHP are supported today). It abstracts away containers by - default, but you can build your own containers if you need to. + Go, Rust, C#, PHP are supported today). It abstracts away containers + by default, but you can build your own containers if you need to.

@@ -238,8 +238,8 @@

Wide Language Support

Fission is extensible to any programming language of your choice. - Python, NodeJS, Go, C#, PHP are supported today, but you can build - your own custom containers if you need to. + Python, NodeJS, Go, Rust, C#, PHP are supported today, but you can + build your own custom containers if you need to.

diff --git a/content/en/docs/usage/languages/_index.md b/content/en/docs/usage/languages/_index.md index 47c4d382..67fafde3 100644 --- a/content/en/docs/usage/languages/_index.md +++ b/content/en/docs/usage/languages/_index.md @@ -21,6 +21,7 @@ Each environment has a runtime image (`*-env`) and, where dependency building is | Node.js | `ghcr.io/fission/node-env` | `ghcr.io/fission/node-builder` | [Node.js]({{% ref "nodejs.md" %}}) | | Python | `ghcr.io/fission/python-env` | `ghcr.io/fission/python-builder` | [Python]({{% ref "python.md" %}}) | | Go | `ghcr.io/fission/go-env` | `ghcr.io/fission/go-builder` | [Go]({{% ref "go.md" %}}) | +| Rust | `ghcr.io/fission/rust-env` | `ghcr.io/fission/rust-builder` | [Rust]({{% ref "rust.md" %}}) | | Java (JVM) | `ghcr.io/fission/jvm-env` | `ghcr.io/fission/jvm-builder` | [Java]({{% ref "java.md" %}}) | | Ruby | `ghcr.io/fission/ruby-env` | `ghcr.io/fission/ruby-builder` | [environments repo](https://github.com/fission/environments/tree/master/ruby) | | PHP | `ghcr.io/fission/php-env` | `ghcr.io/fission/php-builder` | [environments repo](https://github.com/fission/environments/tree/master/php7) | diff --git a/content/en/docs/usage/languages/rust.md b/content/en/docs/usage/languages/rust.md new file mode 100644 index 00000000..aee84068 --- /dev/null +++ b/content/en/docs/usage/languages/rust.md @@ -0,0 +1,321 @@ +--- +title: "Rust Functions" +description: "Writing Rust functions with fission" +weight: 10 +--- + +Fission supports Rust as a first-class function language. +Rust functions are compiled by the builder into native server binaries built on [axum](https://docs.rs/axum) and [tokio](https://tokio.rs), so invocations run at native speed with no per-request process startup. + +In this usage guide we'll cover how to use this environment, write functions, and work with dependencies. + +### Before you start + +We'll assume you have Fission and Kubernetes set up. +If not, head over to the [install guide]({{% ref "../../installation/_index.en.md" %}}). +Verify your Fission setup with: + +```bash +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. + +```bash +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](https://github.com/fission/environments/pkgs/container/rust-env/versions?filters%5Bversion_type%5D=tagged) | [ghcr.io/fission/rust-builder](https://github.com/fission/environments/pkgs/container/rust-builder/versions?filters%5Bversion_type%5D=tagged) | + +### Write a simple function in Rust + +A single-file function is one `.rs` file that defines `pub async fn handler`. +Any [axum handler](https://docs.rs/axum/latest/axum/handler/index.html) signature works, so you can use extractors, `Json`, headers, and so on. + +Here is a hello world example (`hello.rs`): + +```rust +use fission_rust::IntoResponse; + +pub async fn handler() -> impl IntoResponse { + "Hello, World!\n" +} +``` + +Create and test the function: + +```bash +fission fn create --name helloworld --env rust --src hello.rs +``` + +Before accessing the function, make sure its package build has succeeded: + +```bash +fission pkg info --name +``` + +Now test it: + +```bash +$ fission fn test --name helloworld +Hello, World! +``` + +{{% notice info %}} +See [here]({{% ref "../triggers/_index.md" %}}) for how to set up different triggers for a Rust function. +{{% /notice %}} + +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](#working-with-dependencies-cargo-projects). + +### 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 + +```rust +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: + +```bash +fission httptrigger create --method GET --url "/" --function +``` + +```bash +$ curl http://$FISSION_ROUTER/ -H 'X-My-Header: foo' +x-my-header: foo +``` + +##### Query string + +```rust +use std::collections::HashMap; +use fission_rust::IntoResponse; +use fission_rust::axum::extract::Query; + +pub async fn handler(Query(params): Query>) -> impl IntoResponse { + params.get("key-name").cloned().unwrap_or_default() +} +``` + +```bash +$ curl "http://$FISSION_ROUTER/?key-name=123" +123 +``` + +##### Request Body + +###### Plain text + +```rust +use fission_rust::IntoResponse; + +pub async fn handler(body: String) -> impl IntoResponse { + body +} +``` + +```bash +$ curl -X POST http://$FISSION_ROUTER/ -d foobar +foobar +``` + +###### JSON + +```rust +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) -> impl IntoResponse { + Json(msg) +} +``` + +```bash +$ curl -X POST http://$FISSION_ROUTER/ \ + -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: + +```rust +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", + ) +} +``` + +```bash +$ curl -i http://$FISSION_ROUTER/ +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`: + +```rust +// util.rs +pub fn greeting() -> String { + "Hello from a sibling module!\n".to_string() +} +``` + +```rust +// handler.rs +use fission_rust::IntoResponse; + +pub async fn handler() -> impl IntoResponse { + crate::util::greeting() +} +``` + +```bash +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: + +```toml +# Cargo.toml +[package] +name = "echo-example" +version = "0.1.0" +edition = "2024" + +[dependencies] +fission-rust = { git = "https://github.com/fission/environments", rev = "" } +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. + +```rust +// 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: + +```bash +$ 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: + +```rust +// 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: + +```bash +fission fn create --name myfn --env rust --pkg mypkg --entrypoint +``` + +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](https://github.com/fission/environments/blob/master/rust/README.md) to learn about rebuilding the environment images. + +### Resource usage + +Like any function, you can bound a Rust function's resources: + +```bash +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=` while benchmarking to find the right values. diff --git a/static/data/environments.json b/static/data/environments.json index c6f16d08..4f14935a 100644 --- a/static/data/environments.json +++ b/static/data/environments.json @@ -32,6 +32,17 @@ } ] }, + { + "name": "Rust", + "logo": "/images/lang-logo/rust-logo.svg", + "repo": "https://github.com/fission/environments/tree/master/rust", + "images": [ + { + "main": "rust-env", + "builder": "rust-builder" + } + ] + }, { "name": "Python", "logo": "/images/lang-logo/python-logo.svg", diff --git a/static/images/lang-logo/rust-logo.svg b/static/images/lang-logo/rust-logo.svg new file mode 100644 index 00000000..7e81e855 --- /dev/null +++ b/static/images/lang-logo/rust-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tools/environments.py b/tools/environments.py index 6636b026..d04d376a 100644 --- a/tools/environments.py +++ b/tools/environments.py @@ -8,6 +8,7 @@ 'jvm-env': 'Java', 'jvm-jersey-env-25': 'Java (JVM-Jersey)', 'ruby-env': 'Ruby', + 'rust-env': 'Rust', 'python-env': 'Python', 'python-fastapi-env': 'Python (FastAPI)', 'binary-env': 'Misc',