Don't Make These Mistakes When Writing Rust
Rust, as a systems programming language known for its emphasis on safety and concurrency, has garnered widespread attention from developers in recent years. However, despite being a powerful and well-designed language, there are still some common bad habits that developers fall into during development. These poor practices can make code difficult to maintain, degrade performance, or even introduce security risks. This article highlights some of these common pitfalls and provides suggestions for improvement. 1. Overusing unwrap and expect Rust's Option and Result types provide robust error-handling capabilities. However, many beginners often overuse the unwrap and expect methods for convenience. These methods will cause the program to panic immediately when encountering None or Err. Bad Example: fn read_file(path: &str) -> String { std::fs::read_to_string(path).unwrap() } Suggested Improvement: Take full advantage of match statements or if let expressions to handle possible errors, rather than simply using unwrap. fn read_file(path: &str) -> Result { std::fs::read_to_string(path) } 2. Ignoring Ownership and Lifetimes Rust's ownership and lifetime system is one of its standout features, but also one of the most misunderstood and misused. Ignoring ownership and lifetimes can lead to compiler errors, memory leaks, or dangling references. Bad Example: fn get_longest_line longest_line.len() { longest_line = line; } } longest_line } This code attempts to return a reference to a string slice, but that reference might point to a resource that has already been released, leading to a dangling reference. Suggested Improvement: Return a copy of the data or use other strategies to manage data lifetimes properly. fn get_longest_line(lines: &[&str]) -> String { let mut longest_line = ""; for line in lines { if line.len() > longest_line.len() { longest_line = line; } } longest_line.to_string() } 3. Improper Use of clone and copy Due to Rust's ownership model, developers sometimes overuse the clone method to pass data between scopes. This not only degrades performance but can also lead to unnecessary memory usage. Bad Example: fn process_data(data: Vec) { // ... some processing ... } fn main() { let data = vec![1, 2, 3, 4, 5]; process_data(data.clone()); // Unnecessary cloning } Suggested Improvement: Pass data references via borrowing instead of cloning entire data structures. fn process_data(data: &[i32]) { // ... some processing ... } fn main() { let data = vec![1, 2, 3, 4, 5]; process_data(&data); // Passing a reference instead of cloning } 4. Inappropriate Use of mut In Rust, variables are immutable by default. However, for convenience, developers might overuse the mut keyword to make variables mutable, which can make the code harder to understand and maintain. Bad Example: fn main() { let mut x = 5; // ... some code that may or may not change x ... } Suggested Improvement: Prefer using immutable variables and only use mut when you truly need to change the value. fn main() { let x = 5; // Immutable by default // ... code that doesn't change x ... } If variable mutation is needed, it should be clearly indicated and confined to a localized code block. 5. Ignoring Compiler Warnings and Clippy Suggestions The Rust compiler and the Clippy linter provide many helpful warnings and suggestions, but developers sometimes ignore them. Bad Example: Compiler shows warnings such as unused variables or unhandled errors, but the developer chooses to ignore them. Suggested Improvement: Take every warning and suggestion seriously and try to resolve them. This not only improves code quality but also helps prevent potential issues. 6. Misusing Macros Rust’s macro system is extremely powerful and can be used to write flexible and efficient code. However, abusing macros can lead to code that is difficult to read and maintain. Bad Example: macro_rules! print_something { () => { println!("Something"); }; } fn main() { print_something!(); // Using a macro to print a simple string } In this example, using a macro to print a simple string is overkill. Suggested Improvement: Avoid using macros unless you need dynamic code generation or complex logic reuse. In the above case, a regular function is clearer: fn print_something() { println!("Something"); } fn main() { print_something(); // Direct function call } 7. Poor Module and Struct Design Good module and struct design is essential for code readability and maintainability. However, developers sometimes cram too much functionality into a sin

Rust, as a systems programming language known for its emphasis on safety and concurrency, has garnered widespread attention from developers in recent years. However, despite being a powerful and well-designed language, there are still some common bad habits that developers fall into during development. These poor practices can make code difficult to maintain, degrade performance, or even introduce security risks. This article highlights some of these common pitfalls and provides suggestions for improvement.
1. Overusing unwrap
and expect
Rust's Option
and Result
types provide robust error-handling capabilities. However, many beginners often overuse the unwrap
and expect
methods for convenience. These methods will cause the program to panic immediately when encountering None
or Err
.
Bad Example:
fn read_file(path: &str) -> String {
std::fs::read_to_string(path).unwrap()
}
Suggested Improvement:
Take full advantage of match
statements or if let
expressions to handle possible errors, rather than simply using unwrap
.
fn read_file(path: &str) -> Result<String, std::io::Error> {
std::fs::read_to_string(path)
}
2. Ignoring Ownership and Lifetimes
Rust's ownership and lifetime system is one of its standout features, but also one of the most misunderstood and misused. Ignoring ownership and lifetimes can lead to compiler errors, memory leaks, or dangling references.
Bad Example:
fn get_longest_line<'a>(lines: &'a Vec<&'a str>) -> &'a str {
let mut longest_line = "";
for line in lines {
if line.len() > longest_line.len() {
longest_line = line;
}
}
longest_line
}
This code attempts to return a reference to a string slice, but that reference might point to a resource that has already been released, leading to a dangling reference.
Suggested Improvement:
Return a copy of the data or use other strategies to manage data lifetimes properly.
fn get_longest_line(lines: &[&str]) -> String {
let mut longest_line = "";
for line in lines {
if line.len() > longest_line.len() {
longest_line = line;
}
}
longest_line.to_string()
}
3. Improper Use of clone
and copy
Due to Rust's ownership model, developers sometimes overuse the clone
method to pass data between scopes. This not only degrades performance but can also lead to unnecessary memory usage.
Bad Example:
fn process_data(data: Vec<i32>) {
// ... some processing ...
}
fn main() {
let data = vec![1, 2, 3, 4, 5];
process_data(data.clone()); // Unnecessary cloning
}
Suggested Improvement:
Pass data references via borrowing instead of cloning entire data structures.
fn process_data(data: &[i32]) {
// ... some processing ...
}
fn main() {
let data = vec![1, 2, 3, 4, 5];
process_data(&data); // Passing a reference instead of cloning
}
4. Inappropriate Use of mut
In Rust, variables are immutable by default. However, for convenience, developers might overuse the mut
keyword to make variables mutable, which can make the code harder to understand and maintain.
Bad Example:
fn main() {
let mut x = 5;
// ... some code that may or may not change x ...
}
Suggested Improvement:
Prefer using immutable variables and only use mut
when you truly need to change the value.
fn main() {
let x = 5; // Immutable by default
// ... code that doesn't change x ...
}
If variable mutation is needed, it should be clearly indicated and confined to a localized code block.
5. Ignoring Compiler Warnings and Clippy Suggestions
The Rust compiler and the Clippy linter provide many helpful warnings and suggestions, but developers sometimes ignore them.
Bad Example:
Compiler shows warnings such as unused variables or unhandled errors, but the developer chooses to ignore them.
Suggested Improvement:
Take every warning and suggestion seriously and try to resolve them. This not only improves code quality but also helps prevent potential issues.
6. Misusing Macros
Rust’s macro system is extremely powerful and can be used to write flexible and efficient code. However, abusing macros can lead to code that is difficult to read and maintain.
Bad Example:
macro_rules! print_something {
() => {
println!("Something");
};
}
fn main() {
print_something!(); // Using a macro to print a simple string
}
In this example, using a macro to print a simple string is overkill.
Suggested Improvement:
Avoid using macros unless you need dynamic code generation or complex logic reuse. In the above case, a regular function is clearer:
fn print_something() {
println!("Something");
}
fn main() {
print_something(); // Direct function call
}
7. Poor Module and Struct Design
Good module and struct design is essential for code readability and maintainability. However, developers sometimes cram too much functionality into a single module or struct, making the code difficult to understand and extend.
Bad Example:
pub struct Monster {
health: i32,
attack: i32,
defense: i32,
// ... too many other fields and methods ...
}
impl Monster {
// ... too many methods ...
}
Suggested Improvement:
Split large modules or structs into smaller, more focused components. Use composition and delegation to organize code in a modular way.
pub struct MonsterStats {
health: i32,
attack: i32,
defense: i32,
}
pub struct MonsterBehavior {
// ... fields focused on behavior ...
}
pub struct Monster {
stats: MonsterStats,
behavior: MonsterBehavior,
}
8. Neglecting Documentation Comments
Rust supports rich documentation comments, which are extremely helpful for library users and collaborators. Unfortunately, these comments are often neglected or omitted.
Bad Example:
// No documentation comment
pub fn complex_calculation(x: i32, y: i32) -> i32 {
// ... some complex calculation ...
}
Suggested Improvement:
Add documentation comments for every public module, struct, enum, function, and method. Use ///
for item-level docs, and //!
for module-level or file-level documentation.
/// Performs a complex calculation and returns the result.
///
/// # Parameters
/// * `x` - The first input value.
/// * `y` - The second input value.
///
/// # Returns
/// The result of the calculation.
pub fn complex_calculation(x: i32, y: i32) -> i32 {
// ... some complex calculation ...
}
9. Inadequate Testing
Testing is key to ensuring code quality and correctness, but is often overlooked or done inadequately.
Bad Example:
Only simple unit tests are written, without covering edge cases and error handling.
Suggested Improvement:
Write comprehensive unit tests, including normal cases as well as edge conditions and error scenarios. Use Rust’s built-in test framework to organize tests, and aim for high code coverage.
#[test]
fn test_complex_calculation() {
assert_eq!(complex_calculation(10, 20), 30); // Sample test, adapt to actual logic
// ... more test cases ...
}
Conclusion
This article has summarized several common bad habits in Rust development and offered suggestions for improvement. Avoiding these habits can enhance code readability and maintainability, while also reducing potential security risks and performance issues.
We are Leapcell, your top choice for hosting Rust projects.
Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:
Multi-Language Support
- Develop with Node.js, Python, Go, or Rust.
Deploy unlimited projects for free
- pay only for usage — no requests, no charges.
Unbeatable Cost Efficiency
- Pay-as-you-go with no idle charges.
- Example: $25 supports 6.94M requests at a 60ms average response time.
Streamlined Developer Experience
- Intuitive UI for effortless setup.
- Fully automated CI/CD pipelines and GitOps integration.
- Real-time metrics and logging for actionable insights.
Effortless Scalability and High Performance
- Auto-scaling to handle high concurrency with ease.
- Zero operational overhead — just focus on building.
Explore more in the Documentation!
Follow us on X: @LeapcellHQ