SoFunction
Updated on 2025-04-09

Rust's generics, Traits and life cycle usage and description

1. The original intention of eliminating code duplication

In traditional programming, if we need to find the maximum value for two different integer lists separately, it is easy to copy and paste the same logical code.

For example:

The following example (Listing 10-1) shows how to find the maximum value from a list of integers:

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let mut largest = &number_list[0];

    for number in &number_list {
        if number > largest {
            largest = number;
        }
    }
    println!("The largest number is {}", largest);
}

When we need to do the same operation on multiple lists, simply copying this code not only seems repetitive, but also increases the complexity of maintenance.

If we modify the logic to other forms, we must update all duplicate codes synchronously.

2. Extract functions to implement code reuse

To solve this problem, we can extract the logic of "finding the maximum value" into a function, making the code not only clearer, but also reduce maintenance costs.

After reconstruction, the code is as follows (Listing 10-3):

fn largest(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for &item in () {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let result = largest(&number_list);
    println!("The largest number is {}", result);
}

Step summary

  1. Identify duplicate codes: Similar logic appears in multiple places.
  2. Extract into a function: Abstract the repeated code into a separate function and pass the data through parameters.
  3. Calling a new function: Use function calls everywhere instead of copied code.

This approach is not only suitable for the current integer type, but also lays the foundation for the next introduction of generics.

3. Generics: Make functions suitable for multiple data types

The function we just didlargestThe parameters are limited to&[i32], but in reality, if there is a similar requirement to find the maximum value in the character array, we don't have to rewrite the logic.

Rust's generics allow us to parameterize types, thus writing a function that can adapt to different data types.

For example:

Suppose we want to write a function that can be processed at the same timei32charEven other comparable data types, just use generic parameters in the function definition and specify in the constraint that some trait must be implemented (e.g.PartialOrdUsed to compare sizes):

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in () {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let char_list = vec!['y', 'm', 'a', 'q'];
    
    let largest_number = largest(&number_list);
    let largest_char = largest(&char_list);
    
    println!("The largest number is {}", largest_number);
    println!("The largest char is {}", largest_char);
}

In this example, we use genericsTReplace the specific type, and useT: PartialOrd + CopyLimits the ability of generics to achieve size comparison and copying.

This ensures that no matter what data type is passed in, the function will work properly as long as it satisfies these traits.

4. Traits and Lifecycle: Further Abstraction and Security

Although this article mainly revolves around generics, Rust's other two core concepts -Traitsandlife cycleIt's equally important.

  • Traits: By defining an interface, Traits describes the behavior that a type must have. With Traits, more advanced abstraction can be implemented, allowing generic code to be only applicable to types that meet certain conditions.
  • life cycle: When it comes to references, lifecycle helps the compiler understand the relationship between different references, thus ensuring the validity of references. By explicitly declaring the lifecycle, we can enable the compiler to better check borrow rules and further improve code security.

Combining generics, Traits and lifecycle, Rust provides extremely high flexibility and security, allowing code to be reused and memory-safe.

5. Summary

This article shows how to extract functions from repeated code and then use generics to implement code reuse through the example of "Find Maximum Value in List".

The main steps include:

  • Identify repetitive logic: Clear which codes are duplicate.
  • Abstract Extraction: Encapsulate repeated code into functions, explicit input and output.
  • Generic Applications: Use generic parameters to make functions suitable for multiple types, and ensure that the types meet the necessary behavior through trait constraints.
  • Further expansion: Combining Traits and lifecycle allows for more advanced abstraction and memory security.

This abstract process from shallow to deep is an important paradigm in Rust programming. Through continuous abstraction and generalization, we can not only reduce code duplication, but also write more general, robust and maintainable code.

The above is personal experience. I hope this blog can help you better understand the application of generics, Traits and life cycles in Rust, and apply this idea in actual development. I also hope everyone supports me.