Serverless functions in Rust: AWS Lambda – performance without a server
Serverless architecture has become the standard for building scalable and cost-effective applications. However, classic languages for AWS Lambda (Python, Node.js, Java) face the problem of cold start and high memory consumption. Rust, with its zero-cost abstractions and compilation to native code, offers a revolutionary solution: functions that start in milliseconds and consume minimal resources. In this article, we will look at how to create Serverless functions in Rust for AWS Lambda, configure the environment, and optimize performance.
Why Rust for Serverless?
The choice of Rust for AWS Lambda is driven by three key factors:
- Minimal cold start: A Rust binary does not require a virtual machine or interpreter. Startup time is 50–200 ms compared to 1–3 seconds for Python or Java.
- Low memory consumption: Rust functions use 5–15 MB of RAM, allowing you to save on execution costs.
- Memory safety: The ownership system eliminates memory leaks and data races, which are critical for production workloads.
Rust is ideal for latency-critical tasks: processing API requests, ETL processes, real-time analytics.
Setting up the project and tools
For development you will need:
- Rust (install via
rustup) - The
cargo lambdautility (install:cargo install cargo-lambda) - An AWS account and configured AWS CLI
Create a new project:
cargo lambda new my-rust-functioncd my-rust-functionAs a result, a structure will be generated with the src/main.rs file and Cargo.toml with the necessary dependencies:
[package]name = "my-rust-function"version = "0.1.0"edition = "2021"
[dependencies]lambda_runtime = "0.11"serde = { version = "1.0", features = ["derive"] }serde_json = "1.0"tokio = { version = "1", features = ["macros"] }tracing = "0.1"tracing-subscriber = { version = "0.3", features = ["fmt"] }The key dependency is lambda_runtime, which provides the runtime for interacting with AWS Lambda.
Writing your first Serverless function in Rust
Let's look at a simple example: a function that takes JSON with a name and returns a greeting.
Contents of src/main.rs:
use lambda_runtime::{service_fn, LambdaEvent, Error};use serde::{Deserialize, Serialize};
#[derive(Deserialize)]struct Request { name: String,}
#[derive(Serialize)]struct Response { message: String,}
#[tracing::instrument(skip(event), fields(request_id = %event.context.request_id))]async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> { let request = event.payload; let message = format!("Hello, {}! You called a Rust function.", request.name); Ok(Response { message })}
#[tokio::main]async fn main() -> Result<(), Error> { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) .init();
let func = service_fn(handler); lambda_runtime::run(func).await}Code breakdown:
LambdaEvent— a wrapper containing the payload and execution context.#[tracing::instrument]— automatic logging with request ID.service_fn— adapts the async handler for the Lambda runtime.
Build and run locally:
cargo lambda build --releasecargo lambda invoke --data-ascii '{"name": "World"}'Output: {"message":"Hello, World! You called a Rust function."}