Rust:
Refactoring

How to:

Let’s refactor a simple piece of Rust code to make it more idiomatic and maintainable. We start with a function that calculates the sum of a vector of integers:

fn sum(vec: &Vec<i32>) -> i32 {
    let mut sum = 0;
    for i in vec {
        sum += i;
    }
    sum
}

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    println!("The sum is {}", sum(&numbers));
}

Output:

The sum is 15

Now, let’s refactor this to use more idiomatic Rust by leveraging iterators and the fold method:

fn sum(vec: &[i32]) -> i32 {
    vec.iter().fold(0, |acc, &x| acc + x)
}

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    println!("The sum is {}", sum(&numbers));
}

No change in output—it’s still 15—but the refactored version is cleaner and uses Rust’s strengths like borrowing and iterator methods.

Deep Dive

Refactoring has its roots in the Smalltalk community and was popularized in the Java world by Martin Fowler’s book “Refactoring: Improving the Design of Existing Code”. Its principles are universal and apply to Rust as well, where safety and concurrency are paramount. Rust encourages writing robust code by catching issues at compile time, so during refactoring, the Rust compiler acts as a safety net.

Alternatives to manual refactoring include using automated tools, such as ‘rustfmt’ for code formatting and ‘clippy’ for linting, which can suggest more idiomatic ways of writing code. However, deep refactoring often requires a thoughtful understanding of the code’s design, which these tools cannot fully automate.

In Rust, refactoring might revolve around improving type usage, leveraging lifetimes effectively, reducing unnecessary allocations, or employing concurrency patterns like using Arc<Mutex<T>> when necessary. It’s also common to transition from unwrap() to more expressive error handling with Result<T, E>.

See Also

To further dive into refactoring in Rust: