Serverless Functions in Rust: AWS Lambda from A to Z

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why Rust for Serverless is a Breakthrough

\n\n

Serverless architecture has long become the standard for building scalable and cost-effective applications. However, the traditional choice of languages (Python, Node.js, Java) often faces the problem of cold start and high memory consumption. Rust, with its zero-cost abstractions and lack of a garbage collector, offers a revolutionary solution. In this article, we will break down how to create, configure, and deploy serverless functions in Rust for AWS Lambda — from tool installation to production releases.

\n\n

By the end of this article, you will be able to:

\n
    \n
  • Set up the environment for Rust + AWS Lambda development
  • \n
  • Write an efficient function with event handling
  • \n
  • Optimize the binary for minimal cold start time
  • \n
  • Deploy the function to AWS using Cargo Lambda
  • \n
\n\n

Environment Setup: Cargo Lambda and AWS CLI

\n\n

To work with Rust on AWS Lambda, we need a special tool — cargo-lambda. It automates building, testing, and deployment. Let's install it via Cargo:

\n\n
cargo install cargo-lambda
\n\n

Also, make sure you have aws-cli (configured with IAM keys) and Docker (for local testing) installed. Let's create a new project:

\n\n
cargo lambda new my-rust-lambda\ncd my-rust-lambda
\n\n

The command will create a basic structure with a Cargo.toml file and src/main.rs. Pay attention to the dependencies in Cargo.toml:

\n\n
[package]\nname = "my-rust-lambda"\nversion = "0.1.0"\nedition = "2021"\n\n[dependencies]\nlambda_runtime = "0.11"\ntokio = { version = "1", features = ["macros", "rt-multi-thread"] }\nserde = { version = "1.0", features = ["derive"] }\nserde_json = "1.0"\ntracing = { version = "0.1", features = ["log"] }\ntracing-subscriber = { version = "0.3", features = ["fmt"] }
\n\n

The key library is lambda_runtime. It provides the LambdaHandler trait, which we will implement.

\n\n

Writing the First Function: Handling an API Gateway Request

\n\n

Let's consider a classic scenario: a function that receives JSON with a user's name and returns a greeting. Open src/main.rs and write the following code:

\n\n
use lambda_runtime::{run, service_fn, Error, LambdaEvent};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Deserialize)]\nstruct Request {\n    name: String,\n}\n\n#[derive(Serialize)]\nstruct Response {\n    message: String,\n}\n\nasync fn function_handler(event: LambdaEvent<Request>) -> Result<Response, Error> {\n    let name = event.payload.name;\n    let message = format!("Hello, {}! You just launched Rust on Lambda!", name);\n    Ok(Response { message })\n}\n\n#[tokio::main]\nasync fn main() -> Result<(), Error> {\n    tracing_subscriber::fmt()\n        .with_max_level(tracing::Level::INFO)\n        .init();\n\n    run(service_fn(function_handler)).await\n}
\n\n

Let's break down the key points:

\n
    \n
  • LambdaEvent<Request> — a structure containing the request body and execution context.
  • \n
  • #[tokio::main] — the asynchronous runtime required for the Lambda runtime to work.
  • \n
  • serde — deserialization of incoming JSON and serialization of the response.
  • \n
\n\n

Let's test locally:

\n\n
cargo lambda watch
\n\n

Then in another terminal:

\n\n
cargo lambda invoke --data-ascii '{"name": "Alexey"}'
\n\n

You should see: {"message":"Hello, Alexey! You just launched Rust on Lambda!"}

\n\n

Optimizing Cold Start and Binary Size

\n\n

The main advantage of Rust is minimal cold start. However, to achieve this, you need to build the bi

Blogs

Book Recommendations