24.10 Inspecting Generated Assembly with Compiler Explorer
For developers transitioning from C or C++ to Rust, understanding the machine code generated by the Rust compiler can be particularly insightful. It allows you to verify how Rust’s high-level abstractions translate to low-level instructions, often demonstrating their “zero-cost” nature. This can also be useful when diagnosing highly specific performance issues or simply satisfying curiosity about the compiler’s behavior.
A widely-used online tool for this purpose is Compiler Explorer, created by Matt Godbolt, and accessible at https://godbolt.org/. This interactive tool supports Rust (among many other languages) and allows you to write Rust code and instantly see the corresponding assembly output generated by various versions of the Rust compiler for different target architectures. You can also see the output with different optimization levels.
How It’s Useful for Rust Programmers:
- Demystifying Abstractions: See how features like iterators, closures,
Option<T>
,Result<T, E>
, and trait objects compile down. For example, you can observe how an iterator chain often compiles to the same efficient loop as a manually written C-style loop. - Comparing with C/C++: Directly compare the assembly output of similar Rust and C/C++ snippets.
- Learning about Optimizations: Observe the effects of different optimization flags (e.g.,
-O
,-C target-cpu=native
) on the generated code.
Example Exploration:
Consider comparing the assembly for these two functions, which sum the elements of a slice:
// Example to explore in Compiler Explorer
#[no_mangle]
pub fn sum_with_iterator(slice: &[i32]) -> i32 {
slice.iter().sum()
}
#[no_mangle]
pub fn sum_with_loop(slice: &[i32]) -> i32 {
let mut sum = 0;
for i in 0..slice.len() {
sum += slice[i];
}
sum
}
By pasting this code into Compiler Explorer and selecting a Rust compiler (e.g., a recent stable version with optimizations enabled like -O
), you can examine the generated assembly for both functions. You’ll often find that sum_with_iterator
produces code that is just as efficient as, or even identical to, sum_with_loop
, showcasing Rust’s ability to optimize high-level patterns effectively.
Adding the #[no_mangle] attribute to Rust functions helps preserve their original names in the assembly output, making them easier to recognize. Using the -O optimization flag ensures the compiler generates only the necessary code.
While a deep dive into assembly language is beyond this chapter’s scope, Compiler Explorer offers an accessible way to “look under the hood.” For systems programmers, this can be an invaluable resource for building a deeper understanding and confidence in Rust’s performance characteristics and code generation strategies.