Skip to content
Field Notes
Go back

Go & Rust for Cost-Effective Serverless: What the Data Actually Shows

Are people actually squeezing real cost savings out of Go and Rust in serverless infrastructure? Yes — and the numbers are significant enough that this has become a serious trend rather than enthusiast experimentation.


TL;DR

Go and Rust consistently deliver the lowest cold-start times and memory footprints across serverless platforms. On AWS Lambda specifically, real-world rewrites show cost reductions of 70-75% compared to Node.js/TypeScript equivalents. The sweet spot is ARM64 (AWS Graviton) with either language — lower compute price + better performance. The main trade-off is not cost or performance, but developer ergonomics and team familiarity.


Context

The serverless cost model punishes slow startups and high memory usage twice: once in latency, once in the bill. Languages with a garbage collector (Node.js, Python, Java, even Go to a lesser extent) introduce unpredictable latency spikes under load. Rust and Go eliminate or minimize this — Rust has no GC at all, Go’s is fast and tunable. Combined with the move toward ARM64 infrastructure, this is driving real adoption in cost-sensitive engineering teams.


Analysis / Key Findings

1. AWS Lambda: The Benchmark Picture

The most comprehensive ongoing benchmark is lambda-perf which tracks cold starts across all runtimes. Key numbers from late 2025:

RuntimeCold Start (ms)Architecture
Rust (custom runtime)~20 msarm64
Go (provided.al2023)~40 msarm64
Node.js 22~180–300 msarm64
Python 3.12~200–400 msarm64
Java 21 (GraalVM)~300–600 msarm64
Java 21 (JVM)~1000–3000 msarm64

Rust cold starts are 5-8x faster than interpreted runtimes. Go sits comfortably in second place, still dramatically faster than the Node/Python tier.

Critical 2025 change: In August 2025, AWS started billing for the Lambda INIT phase (previously free). For functions with heavy startup logic, this can increase Lambda costs by 10–50%. This single change made Go/Rust even more attractive economically.

2. ARM64 / Graviton: The Multiplier

ARM64 instances on Lambda cost ~20% less than x86 by default. Combined with Rust’s efficiency:

The recommendation from multiple teams is now: arm64 should be the default for all Lambda deployments, and Go/Rust maximize that advantage.

3. Real-World Cost Numbers

The most concrete public data point is a case study published in December 2025:

A microservice handling 2 million requests/day with API gateway functionality:

  • Node.js/TypeScript: $847/month
  • Go: reduced significantly (~$300–400 range reported)
  • Rust: $214/month

Stop Assuming Efficiency: A Real-World AWS Cost Study (Go vs Rust)

Extrapolated: at 100,000 requests, cost per run is approximately $0.03 with Rust vs $2.45 with TypeScript — roughly 80x cheaper at the per-request level.

Memory usage matters too: Rust typically uses 20-30% less memory than equivalent Go services, which directly affects Lambda pricing (billed per GB-second).

4. Go vs Rust: When Each Wins on Lambda

They’re not interchangeable for all scenarios:

ScenarioWinnerReason
Infrequently invoked functionsRustLower cold start cost, now billed
Short-lived compute tasksRustSub-25ms init, minimal memory
High-throughput streaming / computeRustNo GC pauses at all
API gateway / routing / proxyingGo~equal perf, much faster to write
Data processing pipelinesGoGreat stdlib, easier concurrency
Very high memory (3GB+)GoGC overhead disappears at high memory
Team productivityGoSimpler language, faster iteration

Go’s garbage collector does cause occasional latency spikes under heavy load — not catastrophic, but measurable at p99. Rust has zero GC pauses by design.


5. How People Are Structuring These Services

Pattern A: AWS Lambda + Custom Runtime (Rust)

Rust doesn’t have a managed Lambda runtime, so you compile a bootstrap binary and use provided.al2023:

# Cargo.toml
[dependencies]
lambda_runtime = "0.13"
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
use lambda_runtime::{service_fn, LambdaEvent, Error};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct Request { name: String }

#[derive(Serialize)]
struct Response { message: String }

async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
    Ok(Response {
        message: format!("Hello, {}!", event.payload.name),
    })
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    lambda_runtime::run(service_fn(handler)).await
}

Build for Lambda’s ARM64 target:

cargo lambda build --release --arm64
cargo lambda deploy

The cargo-lambda tool (cargo-lambda.info) handles cross-compilation and deployment. Highly recommended.

Pattern B: AWS Lambda + Go (provided.al2023)

Go has official AWS SDK and Lambda runtime support:

package main

import (
    "context"
    "github.com/aws/aws-lambda-go/lambda"
)

type Request struct { Name string `json:"name"` }
type Response struct { Message string `json:"message"` }

func handler(ctx context.Context, req Request) (Response, error) {
    return Response{Message: "Hello, " + req.Name + "!"}, nil
}

func main() {
    lambda.Start(handler)
}

Build:

GOOS=linux GOARCH=arm64 go build -o bootstrap main.go
zip function.zip bootstrap
aws lambda update-function-code --function-name myFunc --zip-file fileb://function.zip

Pattern C: Containerized Services (Cloud Run / Fly.io)

For services that need persistent connections, long processing, or aren’t a good fit for function-per-endpoint architecture, containers on managed platforms are the alternative.

Google Cloud Run autoscales to zero (true serverless billing), handles HTTP routing, and accepts any container image. A Rust service with Axum or Actix-web fits well here:

FROM rust:1.80 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bookworm-slim
COPY --from=builder /app/target/release/myservice /usr/local/bin/
CMD ["myservice"]

Final image: typically 15-50MB for Rust (vs 500MB+ for Node.js). Cloud Run bills per CPU-second and memory-second — a 15MB Rust binary with 128MB RAM allocation costs a fraction of an equivalent Node app at 512MB.

Fly.io is popular for Go services — deploys globally across Fly’s edge network, scales to zero between requests, and has a generous free tier. The Go HTTP server starts fast enough that the “cold” container resume time isn’t painful.


6. Cloudflare Workers: Rust at the Edge

Cloudflare Workers runs JavaScript/WASM at the network edge in ~300 locations worldwide. Rust compiles to WASM and runs here with sub-10ms cold starts globally.

use worker::*;

#[event(fetch)]
async fn main(req: Request, env: Env, _ctx: Context) -> Result<Response> {
    let name = req.path();
    Response::ok(format!("Hello from the edge, {}!", name))
}

The honest picture: Rust/WASM in Workers is best for compute-heavy edge tasks — JWT validation, image processing, cryptography, data transformation. For simple routing or header manipulation, JavaScript is faster to write and performance-equivalent. The WASM memory model requires data copying between JS and WASM memory, which adds overhead for lightweight tasks.

Where it shines: computationally intensive work that previously would need a server round-trip can be handled at the edge node closest to the user, with near-zero latency and Cloudflare’s pricing model (100k free requests/day, $0.30/million after).


7. Web Framework Landscape

For teams building full HTTP services (not just Lambdas):

Rust:

FrameworkFocusNotes
AxumModern, ergonomicBuilt on Tokio; best DX; community favorite 2025
Actix-webRaw throughputSlightly faster benchmarks; older design
RocketEase of useGood for beginners, less control

Axum vs Actix-web is the main debate. Actix-web leads in raw throughput; Axum is within 5-10% but integrates more naturally with the async Rust ecosystem (same Tokio foundation). Most new projects choose Axum for its cleaner API.

Go:

FrameworkFocusNotes
net/http (stdlib)SimplicityProduction-ready, no deps, idiomatic
ChiLightweight routingComposable middleware, stdlib-compatible
GinFull-featuredMost popular, slightly opinionated
EchoPerformanceSimilar to Gin, good for REST APIs
FiberExpress-likeFastest benchmarks, non-stdlib HTTP

For Lambda and small services, Go’s net/http stdlib with no external framework is common and sensible. For larger APIs, Chi or Gin add routing ergonomics without much overhead.


8. The Graviton / ARM64 Strategy in Full

The combination of ARM64 hardware + Go or Rust is now a documented cost optimization strategy, not just a curiosity:

  1. Lambda ARM64: 20% cheaper per GB-second than x86, better performance per watt
  2. EC2 Graviton3: 40% better price/performance than comparable x86 instances for compiled workloads
  3. ECS/Fargate ARM64: Same discount applies to container workloads
  4. Go and Rust cross-compile trivially to ARM64 — a one-line change in the build

The TechRadar benchmark summary (2025): Rust on ARM64 delivers 4.5x the throughput of the same Rust code on x86 in some compute-bound Lambda scenarios, at 30% lower cost. The combination is described as “the optimal serverless configuration” for latency-sensitive workloads.


9. Adoption Signal

This is no longer experimental:

The Go path is more mature and has more community resources for AWS specifically. Rust is catching up fast, driven largely by the Cargo Lambda tooling.


10. Billing Models Across Providers

Understanding how each platform charges is as important as the per-unit price — the billing model determines which workload profiles are cheap or expensive, regardless of language.

AWS Lambda

DimensionDetail
Per request$0.20 per million (after 1M free/month)
Duration$0.0000166667 per GB-second (arm64: ~20% cheaper)
INIT phaseBilled since August 2025 — cold start init time now counts
Minimum billing1ms increments
API Gateway+$3.50/million HTTP API calls (often the bigger cost)

The key gotcha: API Gateway costs frequently exceed Lambda compute costs at scale. A Lambda function that costs $50/month in compute might have $300/month in API Gateway charges. Teams working around this use Lambda Function URLs (no extra cost) or Application Load Balancer ($0.008/LCU-hour) as alternatives.

The 2025 INIT billing change hit hardest for infrequently-invoked functions with heavy startup — exactly where Node.js/Python are slow and Go/Rust win most. A Rust function initializing in 20ms vs a Python function taking 400ms on a cold start is now a real line item, not just a latency concern.

Go/Rust advantage here: Short init time + low memory allocation = lowest possible Lambda bill. A 128MB Rust Lambda at 10ms duration costs ~$0.21/million invocations total (compute + requests). The same Python function at 512MB and 200ms runs ~$16.90/million — 80x more expensive.


Google Cloud Run

DimensionDetail
CPU$0.000024/vCPU-second (~$0.086/vCPU-hour)
Memory$0.0000025/GiB-second (~$0.009/GiB-hour)
Requests$0.40 per million (after 2M free/month)
Minimum billing100ms increments
IdleNot billed when scaled to zero
ConcurrencyUp to 1000 concurrent requests per instance

Cloud Run’s billing model is fundamentally different from Lambda: you pay for a container instance while it’s processing requests, but a single instance handles many concurrent requests. This makes it dramatically cheaper than Lambda for sustained traffic, but comparable or more expensive for sporadic invocations.

The high concurrency limit (1000 req/container) strongly favors Go and Rust — both handle concurrent requests efficiently without spawning threads per request. A Go service with goroutines or a Rust service with Tokio can saturate that concurrency limit with very low CPU and memory usage. A Node.js service would struggle to serve 1000 concurrent requests from a single 256MB container.

Go/Rust advantage here: Smaller container images (15-50MB vs 500MB+ for Node) = faster cold starts from zero. Lower memory per request = pack more concurrency per instance = fewer instances = lower bill.


Cloudflare Workers

DimensionDetail
Free tier100,000 requests/day, 10ms CPU/request
Paid (Workers Paid)$5/month flat, then $0.30/million requests
CPU billingPer CPU-millisecond — not wall-clock time
Waiting on I/OFree — you don’t pay while waiting for fetch/DB
KV / Durable ObjectsSeparate pricing
EgressNo egress fees for most Worker traffic

The CPU-time billing model is the critical differentiator. On Lambda, if your function waits 200ms for a database call, you’re billed for those 200ms. On Workers, you pay only for actual CPU work. For I/O-heavy services (most web APIs), this can be 5-10x cheaper than Lambda at equivalent request volumes.

Real-world case: an analytics ingestion pipeline moved from Lambda to Workers and saved $50k/year — the workload was predominantly I/O (receiving events, writing to storage) with minimal CPU, which Lambda bills fully but Workers bills almost nothing for.

The limitation: 10ms CPU time limit on the free tier, 30 seconds on paid (wall-clock). CPU-bound tasks (image processing, crypto, parsing) hit this ceiling. That’s where Rust/WASM earns its place — it fits more computation into the CPU budget than JavaScript would.

Go/Rust advantage here: Go doesn’t run on Workers (no WASM support yet in Workers’ Go runtime). Rust/WASM is the play. For compute-heavy tasks, Rust squeezes more work into the CPU time limit, making previously-impossible edge computation feasible within budget.


Fly.io

DimensionDetail
ComputeFrom $0.0000008/vCPU-second (~$1.94/vCPU-month)
Memory$0.0000096/GB-second (~$1.94/GB-month)
Idle (machines)Billed unless scaled to zero (opt-in)
Egress$0.02/GB after 160GB/month free
Regions~35 global regions, automatic routing

Fly.io is closer to a traditional VPS with fast global deployment than a pure serverless platform. You pay for machine uptime unless you configure scale-to-zero. The value proposition is global proximity — your Go or Rust service runs in the region closest to each user, with the simplicity of fly deploy.

For small services with predictable traffic, the monthly fixed cost is often less than Lambda’s per-invocation costs. For completely sporadic traffic (occasional jobs, webhooks), Lambda or Cloud Run’s true pay-per-use wins.


Side-by-Side: Which Billing Model Fits Which Workload

Workload PatternBest PlatformWhy
Sporadic invocations, low trafficLambdaTrue per-invocation billing
High-volume, I/O-heavy APICloudflare WorkersCPU-only billing, no idle cost
Sustained traffic, concurrent requestsCloud RunPer-instance billing + high concurrency
Global latency-sensitive, edge logicCloudflare Workers300 PoPs, ~5ms to any user
Full app, predictable trafficFly.ioSimplicity + global + flat monthly
Compute-intensive burstsLambda ARM64Scales to zero, scales to infinity

The language choice compounds with the billing model:


Conclusion

The case for Go/Rust in serverless is now well-supported by real numbers:

Decision framework:

The biggest remaining barrier isn’t performance or cost — it’s team familiarity. Go has a gentle enough learning curve that it’s increasingly being adopted as a “primary Lambda language” by Node.js teams. Rust requires more investment but the payoff is proportionally larger for high-traffic services.


References


Share this post on:

Previous Post
SST for Preview Environments and CI/CD in Solo Development
Next Post
Go for TypeScript Developers: A Practical First Steps Guide