TL;DR: Rust is now a first-class citizen on AWS Lambda with the
aws-lambda-rust-runtimecrate and theprovided.al2023custom runtime. Rust Lambda functions have 1–5ms cold starts (vs 100–500ms for Node.js, 300ms–2s for Python), cost 60–80% less than equivalent Node.js functions at scale, and use 20–40MB memory vs 120–200MB for Node.js.
- Cold start: Rust 1–5ms vs Node.js 100–500ms vs Python 300ms–1s
- Memory footprint: Rust ~20–40 MB vs Node.js ~120–200 MB vs Python ~80–150 MB
- Cost: Rust uses 3–5x less memory → 3–5x lower Lambda pricing at same request volume
- Concurrency: Rust handles CPU-heavy work 10–50x faster than Node.js
- Runtime:
provided.al2023custom runtime : compile locally, upload binary
Who Should Read This?
This guide is for backend engineers and DevOps engineers who are evaluating Rust for AWS Lambda and need concrete numbers, working code, and a realistic picture of the tradeoffs. The ideal reader is a software engineer at a US startup or technology company : typically earning $140K–$195K : who already runs Node.js or Python Lambdas and wants to understand whether switching is worth the effort. You should be comfortable with AWS Lambda concepts (triggers, execution environments, IAM roles) and have basic Rust familiarity. You do not need to be a Rust expert : the code patterns in this guide are self-contained.
Why Should You Use Rust for AWS Lambda in 2026?
Rust is the most cost-efficient and fastest-starting language for AWS Lambda : a compiled binary with no runtime overhead produces near-zero cold starts and the smallest memory allocation of any supported language.
AWS Lambda pricing has two components: number of requests ($0.20 per million) and duration × memory allocation ($0.0000166667 per GB-second). Rust's advantage is entirely in the second component: smaller memory = lower cost per second.
Cost comparison for 10M requests/month, 100ms avg duration:
| Language | Memory | GB-seconds | Cost/month |
|---|---|---|---|
| Rust | 32 MB | 32,000 GB-s | $0.53 |
| Node.js 20 | 128 MB | 128,000 GB-s | $2.13 |
| Python 3.12 | 96 MB | 96,000 GB-s | $1.60 |
| Java 21 (SnapStart) | 256 MB | 256,000 GB-s | $4.27 |
The request cost ($2.00 for 10M) is the same for all runtimes : the memory multiplier determines the rest. For high-volume Lambda functions, Rust typically pays for itself within weeks versus Node.js.
Bottom line: At 10M requests/month, Rust Lambda costs $0.53 vs $2.13 for Node.js : a 4x cost reduction from memory efficiency alone, before counting CPU time savings on compute-heavy workloads.
How Do You Build a Rust Lambda Function?
Add aws-lambda-rust-runtime to your Cargo.toml, write an async handler function, compile for the aarch64-unknown-linux-musl target (ARM64 Lambda costs 20% less than x86), and deploy with the AWS CLI.
# Cargo.toml
[package]
name = "my-lambda"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "bootstrap" # Lambda requires the binary to be named "bootstrap"
[dependencies]
lambda_runtime = "0.13"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }// src/main.rs
use lambda_runtime::{run, service_fn, tracing, Error, LambdaEvent};
use serde::{Deserialize, Serialize};
// Input event shape
#[derive(Deserialize)]
struct Request {
name: String,
value: i64,
}
// Output response shape
#[derive(Serialize)]
struct Response {
message: String,
processed_value: i64,
}
// The handler function : called for every Lambda invocation
async fn function_handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
let (payload, _context) = event.into_parts();
// Your business logic here
let processed_value = payload.value * 2;
Ok(Response {
message: format!("Hello, {}!", payload.name),
processed_value,
})
}
#[tokio::main]
async fn main() -> Result<(), Error> {
// Initialize tracing : logs appear in CloudWatch
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_target(false)
.without_time()
.init();
// Start the Lambda runtime event loop
run(service_fn(function_handler)).await
}How Do You Build and Deploy a Rust Lambda?
Use cargo-lambda : the official Rust Lambda build tool : to cross-compile for Amazon Linux 2023 and deploy directly to AWS.
# Install cargo-lambda
cargo install cargo-lambda
# Build for ARM64 Lambda (recommended : 20% cheaper)
cargo lambda build --release --arm64
# Or build for x86_64
cargo lambda build --release
# Deploy to AWS (creates or updates the Lambda function)
cargo lambda deploy my-lambda \
--iam-role arn:aws:iam::123456789:role/my-lambda-role
# Watch and rebuild on code changes (development mode)
cargo lambda watchThe compiled binary is ~2–5 MB, making Lambda deployment packages significantly smaller than Node.js or Python dependencies. Cold starts are directly proportional to package size : smaller package = faster initialization.
Manual deployment alternative:
# Build
cargo build --release --target aarch64-unknown-linux-musl
# Package the binary
cp target/aarch64-unknown-linux-musl/release/my-lambda ./bootstrap
zip lambda.zip bootstrap
# Deploy via AWS CLI
aws lambda create-function \
--function-name my-rust-lambda \
--runtime provided.al2023 \
--architectures arm64 \
--handler bootstrap \
--role arn:aws:iam::123456789:role/lambda-role \
--zip-file fileb://lambda.zip3 spots open this month → Check if you are eligible.
We help experienced developers transition into Rust roles at €80K–€150K+ in Europe or $130K–$200K+ in the US.
How Do You Connect Rust Lambda to AWS Services?
The AWS SDK for Rust (aws-sdk-* crates) provides idiomatic async clients for every AWS service : DynamoDB, SQS, S3, SNS, and more.
# Cargo.toml : AWS SDK dependencies
[dependencies]
lambda_runtime = "0.13"
aws-config = { version = "1", features = ["behavior-version-latest"] }
aws-sdk-dynamodb = "1"
aws-sdk-s3 = "1"
aws-sdk-sqs = "1"
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["macros"] }use aws_sdk_dynamodb::Client as DynamoClient;
use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct Request {
user_id: String,
}
#[derive(Serialize)]
struct Response {
user_data: Option<String>,
}
async fn handler(
dynamo: &DynamoClient,
event: LambdaEvent<Request>,
) -> Result<Response, Error> {
let (payload, _) = event.into_parts();
// Query DynamoDB
let result = dynamo
.get_item()
.table_name("users")
.key("user_id", aws_sdk_dynamodb::types::AttributeValue::S(payload.user_id))
.send()
.await?;
let user_data = result.item()
.and_then(|item| item.get("data"))
.and_then(|attr| attr.as_s().ok())
.cloned();
Ok(Response { user_data })
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let config = aws_config::load_from_env().await;
let dynamo = DynamoClient::new(&config);
// Move the client into the handler closure
run(service_fn(|event| handler(&dynamo, event))).await
}This pattern : initializing AWS clients outside the handler function in main() : is critical for performance. Clients initialized outside the handler are reused across invocations within the same Lambda container, avoiding re-initialization costs on warm invocations.
When Should You Choose Rust vs Node.js for Lambda?
Choose Rust Lambda when cold starts matter, memory costs are significant at scale, or the function does CPU-heavy work. Choose Node.js when you need fast iteration speed, rich npm ecosystem access, or the function is low-traffic.
| Criterion | Rust Lambda | Node.js Lambda |
|---|---|---|
| Cold start sensitivity (API Gateway) | ✅ 1–5ms cold start | ⚠️ 100–500ms cold start |
| Memory-sensitive (cost at scale) | ✅ 20–40 MB typical | ⚠️ 120–200 MB typical |
| CPU-heavy workloads | ✅ 10–50x faster | ❌ Slower for CPU |
| Development speed | ⚠️ Compile times | ✅ Instant refresh |
| npm package ecosystem | ❌ Not available | ✅ Full ecosystem |
| Team Rust knowledge | ✅ Required | Not needed |
| Low-traffic functions | ⚠️ Overkill | ✅ Fine |
The break-even point: For a Lambda function receiving fewer than 100,000 requests/month, the cost difference between Rust and Node.js is under $1/month : use whichever your team is faster with. For functions receiving millions of requests or handling CPU-intensive work, Rust pays off quickly.
Bottom line: Choose Rust Lambda when you have cold-start-sensitive APIs (Rust: 1–5ms vs Node.js: 100–500ms) or high-volume functions where 3–5x lower memory costs meaningfully reduce your AWS bill. Stick with Node.js for low-traffic functions or when the npm ecosystem is critical.
How Do You Use Axum Inside Lambda?
The lambda-http crate translates API Gateway events into standard HTTP requests : run the same Axum application locally as an HTTP server and on Lambda as a serverless function.
[dependencies]
lambda_http = "0.13"
axum = "0.7"
tokio = { version = "1", features = ["macros"] }
serde = { version = "1", features = ["derive"] }use axum::{routing::get, Router, Json};
use lambda_http::{run, Error};
use serde::Serialize;
#[derive(Serialize)]
struct HealthResponse {
status: &'static str,
version: &'static str,
}
async fn health() -> Json<HealthResponse> {
Json(HealthResponse {
status: "healthy",
version: env!("CARGO_PKG_VERSION"),
})
}
async fn hello(axum::extract::Path(name): axum::extract::Path<String>) -> String {
format!("Hello, {}!", name)
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let app = Router::new()
.route("/health", get(health))
.route("/hello/:name", get(hello));
// Run as Lambda when deployed, as HTTP server when local
run(app).await
}Running locally for development:
# Local HTTP server (cargo-lambda simulates the Lambda runtime)
cargo lambda watch
# → http://localhost:9000
# Or run as standard Axum server locally
LAMBDA_RUNTIME_API=local cargo runHow Do You Handle SQS Triggers with Rust Lambda?
Processing SQS messages is one of the most common Lambda use cases : Rust's compile-time deserialization makes SQS message handling type-safe and zero-allocation.
use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use serde::Deserialize;
// AWS SQS event structure
#[derive(Deserialize)]
struct SqsEvent {
#[serde(rename = "Records")]
records: Vec<SqsRecord>,
}
#[derive(Deserialize)]
struct SqsRecord {
body: String,
#[serde(rename = "messageId")]
message_id: String,
#[serde(rename = "receiptHandle")]
receipt_handle: String,
}
// Your domain message
#[derive(Deserialize)]
struct OrderEvent {
order_id: String,
amount_cents: i64,
customer_id: String,
}
async fn process_sqs_event(event: LambdaEvent<SqsEvent>) -> Result<(), Error> {
let (payload, _context) = event.into_parts();
for record in payload.records {
let order: OrderEvent = serde_json::from_str(&record.body)?;
tracing::info!(
order_id = %order.order_id,
message_id = %record.message_id,
"Processing order"
);
// Process the order...
process_order(&order).await?;
}
Ok(())
}
async fn process_order(order: &OrderEvent) -> Result<(), Error> {
// Business logic here
tracing::info!(
order_id = %order.order_id,
amount_cents = order.amount_cents,
"Order processed"
);
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Error> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
run(service_fn(process_sqs_event)).await
}What Do Rust Serverless Engineers Earn at US Companies?
Engineers who specialize in AWS Lambda infrastructure with Rust : architecting serverless systems that serve millions of requests per month : typically earn $155K–$230K at US technology companies in 2026. The combination of AWS infrastructure expertise and Rust proficiency is increasingly common at cloud-native companies, but remains scarce enough to command a premium over standard cloud engineering roles.
What Common Mistakes Do Developers Make When Running Rust on AWS Lambda?
The most expensive mistake is allocating more memory than the function needs because the default of 128MB "feels safe" : Rust functions that use 20–40MB in practice should be configured at 64–128MB to minimize the GB-second cost.
-
Over-provisioning memory: Rust Lambdas typically use 20–40MB. Engineers who leave the memory setting at 512MB or 1GB (common for Java/Node.js functions that were migrated) pay 12–25x more than necessary. Profile your function with X-Ray or CloudWatch metrics and set memory to 1.2–1.5x actual peak usage.
-
Initializing AWS SDK clients inside the handler: Creating a
DynamoClientinside the handler function runs on every cold start and warm invocation. SDK clients are expensive to create (they resolve credentials, create connection pools, and parse endpoints). Initialize them inmain(): outside the handler : so they are reused across invocations in the same container. -
Forgetting to enable ARM64: ARM64 (Graviton2) Lambdas are 20% cheaper than x86_64 and comparably fast for Rust code. The
--arm64flag incargo lambda buildand--architectures arm64in the AWS CLI deployment are easy to overlook on first setup. Audit existing functions and switch architectures where possible. -
Not handling partial SQS batch failures: When processing SQS batches, Lambda retries the entire batch if the handler returns an error: even if 9 of 10 messages processed successfully. Use the
reportBatchItemFailuresLambda function response type to return only the failed message IDs for retry. Without this, successful messages are processed twice on retry. -
Blocking the Tokio runtime with synchronous code: Rust Lambda functions run on Tokio. Calling synchronous blocking code (heavy computation,
std::thread::sleep, synchronous filesystem reads) inside async handlers blocks the runtime thread. Usetokio::task::spawn_blockingfor CPU-heavy work. -
Missing the binary name requirement: Lambda's custom runtime expects the executable to be named
bootstrap. A Cargo.toml[[bin]]section withoutname = "bootstrap"(or not renaming the binary at build time) causes the Lambda to fail with a permissions or not-found error. Always verify the binary name in your Cargo.toml and build output before deploying.
Want a Structured Path to AWS Rust Proficiency?
If you want to build production-quality Rust services : including serverless functions on Lambda : with a structured curriculum and 1:1 coaching, Rustify's 9-week bootcamp covers async Rust, AWS integration patterns, and deployment workflows from first principles. Engineers who complete the program are equipped to design, build, and deploy Rust Lambda functions and full-stack Rust services independently.
Frequently Asked Questions
AWS Lambda does not ship Rust as a managed runtime (unlike Node.js, Python, Java, Go). Instead, you use the provided.al2023 custom runtime and compile your Rust binary to a static binary named bootstrap. The aws-lambda-rust-runtime crate is maintained by AWS and is the official path. cargo-lambda is the community-maintained build tool that AWS actively endorses.
A cold start occurs when Lambda initializes a new container : downloads the code package, starts the runtime, and runs your main() function up to the run() call. For Rust, this involves: extracting the ~2–5 MB binary (fast), starting the static binary (near-instant), and initializing your AWS SDK clients (varies). Total cold start for a minimal Rust Lambda: 5–15ms. For Node.js: 100–400ms. The difference is most visible for latency-sensitive APIs (user-facing endpoints) and less important for background processing (SQS consumers).
ARM64 (Graviton2) is recommended for all new Rust Lambdas: 20% lower cost, comparable performance (sometimes faster for Rust due to Graviton's energy efficiency). Cross-compile with --target aarch64-unknown-linux-musl. The cargo-lambda tool handles this with --arm64.
Yes : via the lambda-http crate, which translates API Gateway events into standard HTTP requests. Axum is the most popular choice: write your Axum app normally, and swap the Tokio server for Lambda's event loop. This lets you run the same code locally as an HTTP server and on Lambda as a function.
SnapStart is AWS's solution for Java cold start problems : it pre-warms containers and snapshots them. It doesn't apply to Rust (provided.al2023 runtime). Rust doesn't need SnapStart : its cold starts are already faster than SnapStart Java.
cargo-lambda provides cargo lambda watch which simulates the Lambda runtime locally, forwarding HTTP requests to your function. For testing with real AWS services, use LocalStack for local DynamoDB/SQS/S3 simulation, or configure your development AWS profile to use a sandbox account. The cargo lambda invoke command lets you test your function with a JSON payload from the command line.
Lambda supports deployment packages up to 50 MB compressed (250 MB uncompressed). A typical Rust Lambda binary with AWS SDK dependencies is 5–15 MB compressed : well within limits. You will not hit the size limit with normal Rust Lambda workloads. If you embed large static data (ML model weights, lookup tables), use S3 for storage and fetch at cold start.
Set your Lambda timeout to slightly above your expected p99 execution time. Implement a timeout budget inside the handler using LambdaEvent's context : context.deadline gives you the Unix timestamp when Lambda will forcibly terminate the function. Use tokio::time::timeout with the remaining duration to give your function a chance to clean up and return a partial result before the hard deadline.
Related Glossary Terms
- Async/Await: Lambda handlers are async Rust functions : each invocation is a future
- Tokio: The runtime the aws-lambda-rust-runtime uses to drive handler futures
- Tracing: Structured logs from Lambda handlers appear in CloudWatch via tracing spans
- Serde: Deserializes Lambda event payloads and serializes responses to JSON
- Result: Lambda handlers return
Result:Errcauses Lambda to mark the invocation as failed
Keep Reading
- Rust on Cloudflare Workers: WebAssembly at the Edge
- Rust at FAANG: How Amazon, Google, and Microsoft Use Rust
- VC-Funded Rust Startups to Know in 2026
- Building AI Agents from Scratch in Rust

