001 - Memory Mastery: Zero-Cost Abstractions
Memory Mastery: Zero-Cost Abstractions
When you come from Python, Rust can feel both familiar and strange: you still get expressive, high-level code, but Rust insists on compile-time guarantees and predictable performance.
This lesson focuses on zero-cost abstractions and how they compare to common Python patterns.
Learning goals
By the end, you should be able to:
- Explain what zero-cost abstraction means in Rust.
- Compare Rust iterators to Python generators and list comprehensions.
- Understand closure capture in Rust vs Python.
- Use
IntoIteratortrait bounds in a generic Rust function.
What is a zero-cost abstraction?
A zero-cost abstraction means:
You pay no extra runtime cost for using a high-level abstraction compared to writing a lower-level equivalent by hand.
In Rust, features like iterators, closures, and trait bounds are designed to optimize away at compile time whenever possible.
In Python, abstractions are very ergonomic, but interpretation, dynamic dispatch, and runtime object model overhead still exist.
1) Iterators: Python vs Rust
Python generator expression
nums = range(10)
squared_even = (x * x for x in nums if x % 2 == 0)
result = list(squared_even)
print(result) # [0, 4, 16, 36, 64]
This is lazy until consumed by list(...). It is expressive, but execution happens through Python’s runtime machinery.
Python list comprehension
nums = range(10)
result = [x * x for x in nums if x % 2 == 0]
print(result) # [0, 4, 16, 36, 64]
This is usually faster than a generator + list(...) for this case, but it allocates the full list immediately.
Rust iterator chain
let nums = 0..10;
let result: Vec<_> = nums
.filter(|x| x % 2 == 0)
.map(|x| x * x)
.collect();
println!("{:?}", result); // [0, 4, 16, 36, 64]
Rust typically fuses this chain into efficient loop code during compilation, without intermediate collections between filter and map.
2) Closures: capture behavior
Python closure
def make_closure():
x = 10
return lambda y: x + y
closure = make_closure()
print(closure(5)) # 15
Python closures capture variables from enclosing scope and resolve behavior at runtime.
Rust closure
fn make_closure() -> impl Fn(i32) -> i32 {
let x = 10;
move |y| x + y
}
let closure = make_closure();
println!("{}", closure(5)); // 15
With move, x is captured by value. Because types are known at compile time, Rust can optimize closure usage aggressively.
3) Trait bounds vs duck typing
Python relies on duck typing: if an object behaves like an iterable, a for loop can use it.
Rust encodes that requirement in the type system using trait bounds.
Python
def print_iterable(items):
for item in items:
print(item)
print_iterable([1, 2, 3])
Rust
fn print_iterable<T>(items: T)
where
T: IntoIterator,
T::Item: std::fmt::Display,
{
for item in items {
println!("{}", item);
}
}
print_iterable(vec![1, 2, 3]);
print_iterable([10, 20, 30]);
IntoIterator is checked at compile time, so misuse is caught earlier and there is no runtime trait-check penalty for this abstraction.
Complete runnable Rust example
fn main() {
// Iterator example
let nums = 0..10;
let squared_even: Vec<_> = nums
.filter(|x| x % 2 == 0)
.map(|x| x * x)
.collect();
println!("Squared even numbers: {:?}", squared_even);
// Closure example
let add_ten = make_closure();
println!("Closure result: {}", add_ten(5));
// IntoIterator example
print_iterable(vec![1, 2, 3]);
print_iterable([10, 20, 30]);
}
fn make_closure() -> impl Fn(i32) -> i32 {
let x = 10;
move |y| x + y
}
fn print_iterable<T>(items: T)
where
T: IntoIterator,
T::Item: std::fmt::Display,
{
for item in items {
println!("{}", item);
}
}
Key takeaway
Rust’s abstractions are designed to stay expressive and compile down to efficient machine code. As a Python developer, the biggest shift is moving correctness and performance checks from runtime to compile time.
Next lesson: 002 - Stack vs Heap, Moves, and Clones.
Join the Journey Ahead!
If you're eager to continue this learning journey and stay updated with the latest insights, consider subscribing. By joining our mailing list, you'll receive notifications about new articles, tips, and resources to help you seamlessly pick up Rust by leveraging your Python skills.
Other articles in the series
- 000 - Memory Mastery Roadmap
- 002 - Stack vs Heap, Moves, and Clones
- 003 - Borrowing Rules in Real Functions
- 004 - Lifetimes Without Fear
- 005 - String, &str, and Allocation Patterns
- 006 - Smart Pointers (`Box`, `Rc`, `Arc`, `RefCell`)
- 007 - Interior Mutability and Tradeoffs
- 008 - Data Layout and Cache-Friendly Rust
- 009 - Concurrency Memory Safety (`Send`, `Sync`)
- 010 - Profiling and Benchmarking Rust vs Python