Rust- 泛型

Generics are a way of writing code that abstracts over types. This means that the code can be reused with different types, reducing duplication.

Here’s an example. Consider this function that finds the largest number in a slice:

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

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

    largest
}

This function only works for i32 values. If we want a function that works for both i32 and f64 values, we might duplicate the function like so:

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

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

    largest
}

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

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

    largest
}

But duplicating code is never desirable. This is where generics come in:

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

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

    largest
}

The largest function is now generic over some type T. This T type parameter indicates that the function can work with any type. The T: PartialOrd + Copy part is a trait bound, saying “any type T that implements the PartialOrd and Copy traits”. PartialOrd is what allows comparison of values, and Copy allows values of type T to be copied.

Now, you can find the largest i32 or f64 number in a slice with the same function:

let numbers = [1, 2, 3];
let largest_number = largest(&numbers);
println!("{}", largest_number);  // Outputs: 3

let floats = [1.0, 2.0, 3.0];
let largest_float = largest(&floats);
println!("{}", largest_float);  // Outputs: 3

In addition to functions, you can define structs, enums, and methods to be generic over types.

Note: When using generics, Rust employs monomorphization, a process which involves generating non-generic versions of your generic code for each concrete type they are used with, resulting in fast runtime performance.

Monomorphization is a key part of how Rust handles generics. It’s a process that happens during compilation where the compiler generates specific, non-generic code for each use of your generic code with distinct types.

Let’s consider a simple generic function:

fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
    a + b
}

In your program, if you use this function with both i32 and f64 types like so:

let sum_i32 = add(2, 3);       // T is i32 here
let sum_f64 = add(2.0, 3.0);   // T is f64 here

During the compilation, Rust essentially transforms your generic function into two separate functions, something like this:

fn add_i32(a: i32, b: i32) -> i32 {
    a + b
}

fn add_f64(a: f64, b: f64) -> f64 {
    a + b
}

And replaces the calls to add with calls to these specialized functions:

let sum_i32 = add_i32(2, 3);
let sum_f64 = add_f64(2.0, 3.0);

This process is called monomorphization. The term “monomorphization” comes from “mono” (meaning “single”) and “morph” (meaning “form”). So, it is the process of transforming “many forms” (polymorphic, generic code) into “single forms” (specific, non-generic code).

One of the primary advantages of this is speed. Because the code is specialized for the specific types with which it is used, the compiler can perform low-level optimizations to make the code run as fast as possible. However, this can result in increased code size, as separate functions are generated for each distinct type. This trade-off between speed and code size is often referred to as the “monomorphization time-space trade-off”.

A comprehensive case is as follows:

use std::fmt::{Display, Result};

fn main() {
    let mut v: Vec<i32> = vec![1, 2, 3];

    /*
       泛型结构体
       struct 结构体名称 {
           字段:T,
       }
    */

    let t: Data<i32> = Data { value: 100 };
    println!("值:{}", t.value);     // 输出:值:100

    let t: Data<f64> = Data { value: 66.00 };
    println!("值:{}", t.value);     // 输出:值:66

    /*
       Trait 类似其他语言的接口,都是行为的抽象。
       使用trait关键字用来定义。可以是具体的方法,
       也可以是抽象的方法。

       tarit some_trait {
           抽象方法
           fn method1(&self);

           具体实现的普通方法
           fn method2(&self) {
               方法的具体代码
           }
       }

       impl for 为每个结构体实现某个特质。
    */
    let book = Book {
        id: 1,
        name: String::from("Rust"),
        author: String::from("***"),
    };
    book.Show();    // 输出:ID:1, Name:Rust, Author:***

    /*
        泛型函数,主要是参数类型是泛型,不要求所有参数都必须是泛型参数,
        可以是某一个参数是泛型的。
        fn 函数名称(参数1:T, ...) {
            函数实现代码
        }
     */
    show2(book);    // 输出:ID:1, Name:Rust, Author:***
}

struct Book {
    name: String,
    id: u32,
    author: String,
}

fn show2<T:Display>(t:T) {
    println!("{}", t);
}

impl Display for Book {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        println!("ID:{}, Name:{}, Author:{}", self.id, self.name, self.author);
        let r = Result::Ok(());
        return r;
    }
}

trait ShowBook {
    fn Show(&self);
}

impl ShowBook for Book {
    fn Show(&self) {
        println!("ID:{}, Name:{}, Author:{}", self.id, self.name, self.author);
    }
}

struct Data<T> {
    value: T,
}

你可能感兴趣的:(Rust,rust)