The survey results aren't hype
You see the Stack Overflow Developer Survey drop. Rust is at the top of "Most Loved" for the tenth year in a row. Your team is debating whether to rewrite a service in Rust. You've heard the hype, but you also know hype can be misleading. Why do developers actually love this language that refuses to let you compile code with a dangling pointer?
The popularity isn't a marketing trick. It comes from a specific set of trade-offs that solve real pain points. Rust eliminates entire classes of bugs at compile time. It gives you the performance of C without the memory management headaches. It provides tooling that just works. The survey results reflect a community that has stopped fighting the language and started enjoying the reliability.
Ownership and memory safety
Rust solves a problem most languages ignore: who owns the memory? In Python or JavaScript, a garbage collector runs in the background, hunting for unused memory and deleting it. It works, but it pauses your program. In C or C++, you manage memory manually. You get speed, but you also get segfaults and memory leaks if you make a typo.
Rust takes a third path. It enforces strict rules at compile time. Every value has exactly one owner. When the owner goes out of scope, the value is dropped immediately. No background thread. No manual free calls. The compiler checks the rules before your code runs. If you break the rules, you don't get a runtime crash. You get a compile error.
Think of ownership like a single key to a storage unit. Only one person can hold the key at a time. If you give the key to someone else, you no longer have access. You can't open the unit while someone else has the key. When the last person with the key leaves the building, the storage unit is cleared out. This model prevents data races and double frees without runtime overhead.
fn main() {
// Allocate a String on the heap. s1 holds the pointer and length.
let s1 = String::from("hello");
// Move ownership to s2. s1 is invalidated immediately.
// This prevents double-free bugs without a garbage collector.
let s2 = s1;
// Compiler error E0382: use of moved value.
// s1 cannot be used because s2 owns the data now.
// println!("{}", s1);
// s2 is the sole owner and prints successfully.
println!("{}", s2);
}
When you run String::from, Rust allocates memory on the heap. s1 holds a pointer to that memory. When you assign s2 = s1, Rust doesn't copy the heap data. That would be expensive. Instead, it copies the pointer and marks s1 as invalid. This is a "move." The compiler tracks this. If you try to use s1 later, the compiler rejects you with E0382. This guarantees that only one variable points to the heap data at a time. When s2 goes out of scope, the memory is freed. No leaks. No double frees.
Trust the borrow checker. It usually has a point.
Realistic ownership in functions
Ownership isn't just about variables in main. It applies to function arguments and return values. When you pass a value to a function, you move ownership into that function. The caller can't use the value anymore. This forces you to think about data flow.
/// Represents a user with heap-allocated data.
struct User {
name: String,
email: String,
}
/// Takes ownership of a User to process it.
/// The caller cannot use the User after this call.
fn process_user(user: User) {
// Access fields. user owns the data.
println!("Processing user: {}", user.name);
// user is dropped at the end of this scope.
// Memory is freed immediately.
}
fn main() {
// Create a User on the stack, with Strings on the heap.
let u = User {
name: String::from("Alice"),
email: String::from("alice@example.com"),
};
// Move ownership to process_user.
// u is invalidated after this line.
process_user(u);
// Compiler error E0382: use of moved value.
// u was moved to process_user and is no longer valid.
// println!("{}", u.name);
}
This behavior can feel restrictive at first. You can't just pass data around freely. You need to learn borrowing. Borrowing lets you access data without taking ownership. You can borrow immutably multiple times, or mutably once. The compiler enforces these rules. If you try to borrow mutably while an immutable borrow exists, you get E0502. This prevents reading stale data while it's being modified.
Don't fight the compiler here. Reach for references when you don't need ownership.
Null safety and Option
Another reason for the popularity is null safety. In many languages, variables can be null. Accessing a null variable causes a runtime crash. Rust has no null. Instead, it uses Option<T>. Option is an enum that is either Some(value) or None. The compiler forces you to handle both cases. You can't accidentally dereference a null pointer because the type system makes it impossible.
/// Returns the user name if found, otherwise None.
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("Alice"))
} else {
None
}
}
fn main() {
// Must handle the None case explicitly.
match find_user(1) {
Some(name) => println!("Found {}", name),
None => println!("User not found"),
}
// Compiler error E0308: mismatched types.
// You cannot assign an Option<String> to a String.
// let name = find_user(2);
}
This pattern eliminates null pointer exceptions. You handle missing values at compile time. The code is more robust. You don't need to add defensive checks everywhere. The type system guarantees that you've considered the missing case.
Don't rely on runtime checks. Make impossible states unrepresentable.
Tooling that works
Rust's tooling is a major factor in its popularity. cargo is the package manager and build tool. It handles dependencies, building, testing, and formatting. You don't need to configure complex build scripts. cargo build compiles your code. cargo test runs your tests. cargo run builds and runs in one step.
Dependencies are managed in Cargo.toml. cargo resolves versions and downloads crates. It ensures reproducible builds. You don't get dependency hell. The tooling is consistent across the ecosystem.
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
Convention aside: cargo fmt formats every file the same way. Don't argue style in code reviews. Run cargo fmt and move on. Argue logic, not indentation.
cargo clippy is a linter that catches common mistakes. It suggests improvements. It helps you write idiomatic Rust. The community expects you to run clippy before committing. It catches issues the compiler misses.
Let cargo handle the plumbing. You focus on the logic.
The compiler as a teacher
Rust's compiler is famous for helpful error messages. It doesn't just say "error." It explains what went wrong and often suggests how to fix it. This turns the compiler into a teacher. You learn the language by reading the errors.
You'll hit the borrow checker hard at first. It rejects code that looks fine to a human but has hidden bugs. Error E0502 appears when you try to borrow a value as mutable while an immutable borrow is still active. Error E0507 blocks you from moving data out of a borrowed reference. Error E0382 is the classic "use of moved value." These errors stop you from creating data races and memory bugs.
Convention aside: When you call a function that returns a value you don't need, use let _ = result;. This tells other developers you intentionally discarded the value. It suppresses the warning and documents your intent.
The learning curve is steep. That reputation is accurate. You will fight the compiler for the first few weeks. You will write code that makes logical sense but the compiler rejects. This friction is the price of safety. The compiler forces you to think about ownership, lifetimes, and mutability. Once you internalize these concepts, the friction disappears. You start writing correct code on the first try. The time you spend fighting the compiler is time you don't spend debugging segfaults at 3 AM.
Read the error. The compiler is trying to help.
When to use Rust
Rust isn't the right tool for every job. It shines in specific scenarios. Use Rust when you need memory safety without a garbage collector. Use Rust when you are writing systems code, network servers, or tools where performance and predictable latency are requirements. Use Rust when you want the compiler to catch data races, null pointer dereferences, and buffer overflows before the code runs. Use Python or JavaScript when development speed matters more than runtime performance and you can accept garbage collection pauses. Use C or C++ when you are maintaining a large legacy codebase and cannot afford a full rewrite, though you can interoperate with Rust via FFI.
The compiler is strict for a reason. Pay the upfront cost. The runtime rewards you.