定义

需要用一个函数解决多种不同数据类型的同一个功能的时候,就会用到泛型,比如不同类型的数据要分别相加,如果不使用泛型,就会很繁琐,例如:

fn add_i8(a:i8, b:i8) -> i8 {
    a + b
}
fn add_i32(a:i32, b:i32) -> i32 {
    a + b
}
fn add_f64(a:f64, b:f64) -> f64 {
    a + b
}

fn main() {
    println!("add i8: {}", add_i8(2i8, 3i8));
    println!("add i32: {}", add_i32(20, 30));
    println!("add f64: {}", add_f64(1.23, 1.23));
}

如果使用泛型,就会变得简洁:

fn add<T>(a:T, b:T) -> T {
    a + b
}

fn main() {
    println!("add i8: {}", add(2i8, 3i8));
    println!("add i32: {}", add(20, 30));
    println!("add f64: {}", add(1.23, 1.23));
}

上面这段代码仅用于形象的解释泛型,并不能编译通过,在详解中会解释

详解

定义中的代码中的T,就是泛型参数,这个参数名可以随便定义,但一般使用T,因为Type。

在上面的代码中,为什么会报错呢?是因为上面的代码中T没有做限制,T可以是任何类型的值,但不是所有的值都可以相加,比如abc这种字符串,需要使用 std::cmp::PartialOrd 特征(Trait)对 T 进行限制,特征请在特征章节学习,这里只需要理解是让这里的T实现可以比较的功能,也就是对类型做了限制,使其仅允许为可进行运算or比较的数值

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

    for &item in list.iter() {
        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);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

这是一段标准的错误例子,在 char_list = vec!['y', 'm', 'a', 'q'];这个数组的值并不能进行largest函数中的比较,所以会报错

结构体中使用泛型

在之前的方法章节涉及到了结构体,在结构体中也可以使用泛型:

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
}

这里有两点需要特别的注意:

  • 提前声明,跟泛型函数定义类似,首先我们在使用泛型参数之前必需要进行声明 Point<T>,接着就可以在结构体的字段类型中使用 T 来替代具体的类型
  • x 和 y 是相同的类型,如果不是相同的类型,代码就会报错

如果想让x和y既可以类型相同又可以类型不同,就需要定义两个泛型函数:

struct Point<T,U> {
    x: T,
    y: U,
}
fn main() {
    let p = Point{x: 1, y :1.1};
}

枚举中使用泛型

enum Option<T> {
    Some(T),
    None,
}

Option<T> 是一个拥有泛型 T 的枚举类型,它第一个成员是 Som(T),存放了一个类型为 T 的值。得益于泛型的引入,我们可以在任何一个需要返回值的函数中,去使用 Option<T> 枚举类型来做为返回值,用于返回一个任意类型的值 Some(T),或者没有值 None

enum Result<T, E> {
    Ok(T),
    Err(E),
}

这个枚举和 Option 一样,主要用于函数返回值,与 Option 用于值的存在与否不同,Result关注的主要是值的正确性。

如果函数正常运行,则最后返回一个 Ok(T)T 是函数具体的返回值类型,如果函数异常运行,则返回一个 Err(E)E 是错误类型。例如打开一个文件:如果成功打开文件,则返回 Ok(std::fs::File),因此 T 对应的是 std::fs::File 类型;而当打开文件时出现问题时,返回 Err(std::io::Error)E 对应的就是 std::io::Error 类型。

方法中使用泛型

方法中依然需要提前声明,需要注意的是,这里的 Point<T> 不再是泛型声明,而是一个完整的结构体类型,因为我们定义的结构体就是 Point<T> 而不再是 Point。也就是说,impl此时才是使用泛型的地方,Point里面的T只是为了让rust知道这里定义的是泛型参数。

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

方法中的结构体也可以额外定义新的泛型函数

struct Point<T, U> {
    x: T,
    y: U,
}

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c'};

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

这个例子中,T,U 是定义在结构体 Point 上的泛型参数,V,W 是单独定义在方法 mixup 上的泛型参数,它们并不冲突

最后修改:2024 年 05 月 05 日
如果觉得我的文章对你有用,请随意赞赏