If you find this helpful, please like, bookmark, and follow. To keep learning along, follow this series.
10.1.1 Repeated Code
Let’s look at an example:
fn main(){
let number_list = vec![1,2,3,4,5];
let mut largest = number_list[0];
for &item in number_list.iter(){
if item > largest{
largest = item;
}
}
println!("The largest number is {}", largest);
}
The purpose of this program is to find the largest value in a Vector. Its logic is easy to understand: take the first element as a temporary largest value, then use a loop to compare every element in the Vector. If the current element is greater than the value stored as the largest, assign the current element to largest.
Output:
The largest number is 5
If a new requirement is added at this point and you need to find the largest value in another Vector, you can still write it using the same logic:
fn main(){
let number_list = vec![1,2,3,4,5];
let mut largest = number_list[0];
for &item in number_list.iter(){
if item > largest{
largest = item;
}
}
println!("The largest number is {}", largest);
let number_list = vec![6,7,8,9,10];
let mut largest = number_list[0];
for &item in number_list.iter(){
if item > largest{
largest = item;
}
}
println!("The largest number is {}", largest);
}
But you can see that this way produces far too much repeated code.
Repeated code is easy to get wrong. Once we need to change the logic, we have to make the same change in multiple places.
So it is highly recommended to create abstractions by defining functions. The code looks like this:
fn largest(list: &[i32]) -> i32{
let mut largest = list[0];
for &item in list.iter(){
if item > largest{
largest = item;
}
}
largest
}
fn main(){
let number_list = vec![1,2,3,4,5];
let largest_num = largest(&number_list);
println!("The largest number is {}", largest_num);
let number_list = vec![6,7,8,9,10];
let largest_num = largest(&number_list);
println!("The largest number is {}", largest_num);
}
This declares a function called largest. It takes a slice whose element type is i32, and returns an i32. The logic inside the function is the same as above. Note that the parameter &[i32] is a slice, which is essentially a reference. The specific introduction to slices is in 4.5. Slices (Slice), so I won’t go into it here.
This function can also be written in the following way without changing the logic:
fn largest(list: &[i32]) -> i32{
let mut largest = list[0];
for &item in list{
if item > largest{
largest = item;
}
}
largest
}
Compared with the previous version, this one removes the explicit iterator call .iter(), but it does not affect the code’s behavior, because the slice reference itself implements IntoIterator, so for can iterate over list directly. These two forms are semantically equivalent. Rust’s for loop automatically calls iter() for slices, so the explicit iterator call can be omitted. Which style you choose mainly depends on code style and personal preference.
There is another way:
fn largest(list: &[i32]) -> i32{
let mut largest = list[0];
for item in list{
if *item > largest{
largest = *item;
}
}
largest
}
The biggest difference between this version and the previous two is that it explicitly dereferences item (*item) in order to compare its value.
In the previous two versions, destructuring via dereferencing pattern matching was used. You can think of it like this: &item = &i32, so if both sides drop the &, then item = i32. largest is also of type i32, so the two types match and can be compared directly. Naturally, there is no need to dereference later. If item does not have & in front of it, then item is of type &i32, while largest is of type i32. The two types cannot be compared directly, so you must first dereference it, which means adding * in front of item.
Output:
The largest number is 5
The largest number is 10
10.1.2 Steps to Eliminate Repetition
- Identify repeated code
- Create a function, extract the repeated code into the function body, and specify the function’s inputs and return value in the function signature
- Replace the repeated code with function calls
Top comments (0)