Introduction to Rust for Backend Developers
Rust brings memory safety without garbage collection—something that sounds impossible until you understand the ownership system. I spent years writing backend services in Go and Python, and Rust fundamentally changed how I think about resource management and concurrency.
The learning curve is real. Rust’s borrow checker will reject code that compiles fine in other languages, and initially that feels restrictive. But once the concepts click, you realize those restrictions prevent entire classes of bugs: no null pointer dereferences, no data races, no use-after-free. The compiler is your pair programmer, catching issues at compile time rather than 3am production incidents.
Why Rust for backends?
After running Rust services in production, here’s what stands out:
Memory safety without GC pauses - Go’s GC pauses can spike to 10-100ms under load. Rust has zero GC, giving consistent sub-millisecond latency.
Fearless concurrency - The borrow checker prevents data races at compile time. If your code compiles, concurrent access is safe.
Performance - Rust matches C++ in benchmarks while being vastly more ergonomic. Services I’ve ported from Python run 10-50x faster with 1/10th the memory.
Excellent error handling - Result<T, E> forces you to handle errors explicitly. No more uncaught exceptions in production.
See the Rust Book for a comprehensive introduction, or the Rust by Example for hands-on learning.
Core Concepts: Ownership and Borrowing
Rust’s ownership system is what makes memory safety without GC possible. Every value has a single owner, and when the owner goes out of scope, the value is dropped. Simple, but powerful.
Ownership
fn main() {
let s = String::from("hello");
takes_ownership(s);
// s is no longer valid here - the value moved to the function
// println!("{}", s); // This would fail to compile
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string is dropped here
This prevents use-after-free bugs that plague C/C++. The compiler tracks ownership at compile time.
Borrowing
Instead of moving ownership, you can borrow references:
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // Borrow a reference
println!("The length of '{}' is {}.", s, len); // s is still valid
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s goes out of scope, but since it's a reference, nothing is dropped
Borrowing rules (enforced at compile time):
- You can have either one mutable reference OR multiple immutable references
- References must always be valid (no dangling pointers)
These rules prevent data races. If code compiles, concurrent access is safe.
Read more in the Ownership chapter of the Rust Book.
Web Frameworks: Axum and Tokio
The async ecosystem has matured significantly. Tokio is the de-facto async runtime, and Axum has emerged as my preferred web framework—created by the Tokio team, it leverages Rust’s type system beautifully.
Axum Example
use axum::{
routing::get,
Router,
extract::Path,
Json,
};
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
struct User {
id: u64,
name: String,
}
async fn get_user(Path(id): Path<u64>) -> Json<User> {
// In production, fetch from database
Json(User {
id,
name: format!("User {}", id),
})
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "Hello, World!" }))
.route("/users/:id", get(get_user));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
println!("Listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap();
}
Add to Cargo.toml:
[dependencies]
tokio = { version = "1.40", features = ["full"] }
axum = "0.7"
serde = { version = "1.0", features = ["derive"] }
Other notable frameworks:
- Actix-web - Actor-based, extremely fast (tops TechEmpower benchmarks)
- Rocket - Type-safe, ergonomic, similar to Flask/Express feel
- warp - Filter-based, functional style
For my money, Axum strikes the best balance of performance, ergonomics, and ecosystem integration.
Best Practices from Production
After shipping Rust services that handle millions of requests daily:
-
Master ownership early - Fight the borrow checker now, not in production. The Rust Book chapters 4-10 are essential.
-
Use
tokiofor async - Don’t try to roll your own async runtime. Tokio is production-proven and has excellent diagnostics via tokio-console. -
Choose the right framework - Axum for new projects, Actix-web for maximum performance, Rocket for rapid prototyping.
-
Handle errors properly - Use
Result<T, E>and?operator. Libraries like thiserror and anyhow make error handling ergonomic. -
Test thoroughly - Rust’s testing is built-in:
cargo test. Use criterion for benchmarks. -
Profile before optimizing - Use flamegraph and perf to find actual bottlenecks.
-
Use
clippyandrustfmt-cargo clippycatches common mistakes,cargo fmtmaintains consistent style. -
Leverage the type system - Make invalid states unrepresentable. Use newtypes and the builder pattern liberally.
Database access
For SQL databases, sqlx is excellent—compile-time checked SQL queries:
use sqlx::postgres::PgPoolOptions;
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
let pool = PgPoolOptions::new()
.max_connections(5)
.connect("postgres://user:pass@localhost/db").await?;
// Compile-time verified SQL!
let user: (i64, String) = sqlx::query_as("SELECT id, name FROM users WHERE id = $1")
.bind(1)
.fetch_one(&pool)
.await?;
Ok(())
}
For ORMs, diesel is mature and type-safe. For NoSQL, check out mongodb and redis-rs.
Conclusion
Rust isn’t the easiest language to learn, but the investment pays off. The memory safety guarantees, fearless concurrency, and raw performance make it ideal for backend services where reliability and efficiency matter.
Start small: build a CLI tool, then an API, then something more ambitious. The borrow checker will frustrate you initially, but it’s teaching you to write correct concurrent code—something that’s nearly impossible to verify in other languages.
The ecosystem continues maturing. Web frameworks are excellent, async is stable, and database libraries are production-ready. If you’re building backend services that need to be fast, safe, and scalable, Rust deserves serious consideration.
Resources to continue learning:
- The Rust Book - Official guide, start here
- Rust by Example - Learn by doing
- Tokio Tutorial - Master async Rust
- Jon Gjengset’s YouTube - In-depth Rust explanations
- This Week in Rust - Stay current with ecosystem
- r/rust - Active, helpful community
Rust for backend developers from February 2024, covering ownership, borrowing, and web frameworks.