Introduction: Why Rust is Already Fast, But That's Not the Limit
\n\nRust is rightfully considered one of the most performant systems programming languages. Thanks to zero-cost abstractions and a strict ownership system, Rust code often runs as fast as equivalent C or C++ code. However, even in Rust there are "pitfalls" that can slow down an application, especially for beginner developers.
\n\nIn this article, we will break down the key principles and practical techniques for optimizing Rust applications. You will learn how to profile code, avoid unnecessary allocations, work efficiently with collections, and harness the power of the compiler. The material is aimed at beginners, so all examples will be as simple and clear as possible.
\n\nReady to make your Rust code even faster? Let's go!
\n\n1. Profiling: Measure First, Optimize Later
\n\nBefore optimizing anything, you need to understand what is actually slowing things down. Rust has excellent tools for this.
\n\n1.1. Benchmarks with cargo bench
\n\nThe standard way is to use benchmarks. They allow you to measure the execution time of specific functions.
\n\n// In file benches/my_bench.rs\nuse criterion::{criterion_group, criterion_main, Criterion};\n\nfn fibonacci(n: u64) -> u64 {\n match n {\n 0 => 1,\n 1 => 1,\n n => fibonacci(n-1) + fibonacci(n-2),\n }\n}\n\nfn bench_fib(c: &mut Criterion) {\n c.bench_function("fib 20", |b| b.iter(|| fibonacci(20)));\n}\n\ncriterion_group!(benches, bench_fib);\ncriterion_main!(benches);\n\n\nTo run, add to Cargo.toml:
[dev-dependencies]\ncriterion = "0.5"\n\n[[bench]]\nname = "my_bench"\nharness = false\n\nThen execute cargo bench. You will get precise numbers: execution time, standard deviation, and comparison with previous runs.
1.2. CPU and Memory Profiling
\n\nFor deeper analysis, use:
\n- \n
- perf (Linux) — a kernel-level profiler. \n
- Valgrind — for finding memory leaks and cache analysis. \n
- flamegraph — visualization of code "hot spots". \n
Example of generating a flamegraph:
\ncargo install flamegraph\ncargo flamegraph --bin my_app\n# Open flamegraph.svg in a browser\n\n\nA flamegraph will show which functions consume the most time. This will help you focus on what matters most.
\n\n2. Memory Management: Avoiding Unnecessary Allocations
\n\nHeap allocations are one of the most common causes of slow code. Rust gives you full control over memory, but beginners often overuse Box, Vec, and String.
2.1. Use the Stack Instead of the Heap
\n\nWhenever possible, store data on the stack. For example, instead of a Vec for a small fixed set, use an array:
// Slow: heap allocation\nlet data: Vec<i32> = vec![1, 2, 3, 4, 5];\n\n// Fast: stack\nlet data: [i32; 5] = [1, 2, 3, 4, 5];\n\n\n2.2. Pre-allocate Memory
\n\nIf the size of a collection is known in advance, use with_capacity:
// Bad: many reallocations\nlet mut v = Vec::new();\nfor i in 0..1000 {\n v.push(i);\n}\n\n// Good: one allocation\nlet mut v = Vec::with_capacity(1000);\nfor i in 0..1000 {\n v.push(i);\n}\n\n\n2.3. Avoid Cloning
\n\nCloning strings and large structures is an expensive operation. Use borrowing (&str instead of String):
fn process(s: &str) { /* ... */ }\n\n// Bad: cloning\nlet s = String::from("hello");\nprocess(&s.clone());\n\n// Good: borrowing\nprocess(&s);\n\n\n