您现在的位置是:首页 > 编程 > 

【Rust学习】24

2025-07-25 23:30:44
【Rust学习】24 前言我们将使用泛型来为函数签名、结构体等定义创建一个通用模板,这样它们就可以与多种不同的具体数据类型配合使用。内容现在让我们正式的开始本章的内容,首先,我们将探索如何利用泛型来定义函数、结构体、枚举和方法。之后,我们将进一步讨论泛型是如何影响代码性能的。在函数中定义在定义使用泛型的函数时,我们会将泛型参数置于函数签名中,通常用来指定参数和返回值的数据类型。这种做法提升了代码的

【Rust学习】24

前言

我们将使用泛型来为函数签名、结构体等定义创建一个通用模板,这样它们就可以与多种不同的具体数据类型配合使用。

内容

现在让我们正式的开始本章的内容,首先,我们将探索如何利用泛型来定义函数、结构体、枚举和方法。之后,我们将进一步讨论泛型是如何影响代码性能的。

在函数中定义

在定义使用泛型的函数时,我们会将泛型参数置于函数签名中,通常用来指定参数和返回值的数据类型。这种做法提升了代码的灵活性,为函数的调用者带来了更广泛的适用性,同时有效避免了代码重复的问题。

继续讨论我们的 largest 函数,下面的代码示例展示了两个分别用于到整数切片和字符切片中最大值的函数。接下来,我们将这两个函数合并为一个使用泛型的单一函数。

代码语言:rust复制
fn largest_i2(list: &[i2]) -> &i2 {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

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

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![4, 50, 25, 100, 65];

    let result = largest_i2(&number_list);
    println!("The largest number is {result}");
    assert_eq!(*result, 100);

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

    let result = largest_char(&char_list);
    println!("The largest char is {result}");
    assert_eq!(*result, 'y');
}

largest_i2 函数用于出一个 i2 类型切片中的最大值。largest_char 函数用于在一个 char 类型切片中寻最大值。由于这两个函数的主体代码实际上是相同的,我们将通过引入泛型类型参数,将它们合并为一个单一的泛型函数,以此来消除代码重复。

为了在新的单一函数中参数化类型,我们需要像为函数的值参数命名一样,为类型参数命名。您可以使用任何标识符作为类型参数的名称。但按照惯例,Rust 中的类型参数名称简短,通常只有一个字母,并且遵循 UpperCamelCase(大驼峰命名法)。作为类型的缩写,T 成为了大多数 Rust 程序员的首选。因此,我们将使用 T 作为类型参数的名称。

代码语言:rust复制
fn largest<T>(list: &[T]) -> &T {

我们可以这样理解这个定义:largest 函数对某种类型 T 是泛型的。这个函数接受一个名为 list 的参数,该参数是一个 T 类型的值切片。largest 函数将返回一个引用,指向相同类型 T 的值。

下面的代码呈现了一个泛型版本的 largest 函数定义,该定义在其函数签名中使用了泛型数据类型。该代码同样展示了如何调用该函数,无论是传入 i2 类型的切片还是 char 类型的值。需要注意的是,这段代码目前还无法通过编译,但我们将在本章后续部分对其进行修复。

代码语言:rust复制
fn largest<T>(list: &[T]) -> &T {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![4, 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}");
}

当我们尝试运行的时候,我们将得到以下错误和提示:

代码语言:shell复制
error[E069]: binary operation `>` cannot be applied to type `&T`
  --> src/main.rs:44:17
   |
44 |         if item > largest {
   |            ---- ^ ------- &T
   |            |
   |            &T
   |
help: cider restricting type parameter `T`
   |
40 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
   |             ++++++++++++++++++++++

帮助文本提到了 std::cmp::PartialOrd,这是一个 trait(特征),我们将在下一节讨论 traits。目前,您需要知道的是,这个错误表明 largest 函数的主体并不适用于 T 可能代表的所有类型。因为我们希望在函数体中比较类型 T 的值,所以我们只能使用那些值可以被排序的类型。为了启用比较,标准库提供了 std::cmp::PartialOrd 特征,您可以在类型上实现这个特征。按照帮助文本的建议,我们将 T 有效的类型限制为仅那些实现了 PartialOrd 的类型,这样这个例子就能编译通过了,因为标准库已经在 i2char 上实现了 PartialOrd 特征。

在结构中定义

我们也可以定义结构体,使其中的一或多个字段使用泛型类型参数,采用 <> 语法。下方的示例代码定义了一个 Point<T> 结构体,用来存储任意类型的xy 坐标值。

代码语言:rust复制
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>,所以这个定义表明 Point<T> 结构体是针对某种类型 T 的泛型,字段 xy 都是相同的类型,无论这个类型是什么。如果我们尝试创建一个 Point<T> 的实例,其值是不同类型,如下方代码所示,我们的代码将无法编译。

代码语言:rust复制
struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let wont_work = Point { x: 5, y: 4.0 };
}

当我们尝试运行代码时,我们将得到以下的错误:

代码语言:rust复制
error[E008]: mismatched types
  --> src/main.rs:82:8
   |
82 |     let wont_work = Point { x: 5, y: 4.0 };
   |                                      ^^^ expected integer, found floating-point number

这是因为当我们将整数值 5 分配给 x 时,我们让编译器知道泛型类型 T 对于这个 Point<T> 实例将是整数类型。接着,当我们尝试将 4.0 分配给 y 时,由于 y 被定义为与 x 相同的类型,即整数类型,我们会遇到类型不匹配的错误。

那么如果我们想在一个结构体中,拥有不同的泛型类型,应该如何做呢?很简单,定义多个泛型类型,如下所示:

代码语言:rust复制
struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
}

我们将 Point 的定义更改为类型 TU 的泛型,其中 x 的类型T 的类型yU

在枚举中定义

正如我们对结构体所做的,我们也可以定义枚举来在其成员中持有泛型数据类型。让我们再次看看标准库提供的 Option<T> 枚举:

代码语言:rust复制
#![allow(unused)]
fn main() {
  enum Option<T> {
      Some(T),
      one,
  }
}

现在这个定义对您来说应该更加清晰了。如您所见,Option<T> 枚举是针对类型 T 的泛型,并且它有两个成员:Some,它包含一个类型为 T 的值;以及 one,它不包含任何值。通过使用 Option<T> 枚举,我们可以表达一个可选值的抽象概念。因为 Option<T> 是泛型的,所以无论可选值是什么类型,我们都可以利用这个抽象来处理。

同样的枚举也可以有多个泛型类型:

代码语言:rust复制
enum Result<T, E> {
    Ok(T),
    Err(E),
}

所以当您在代码中遇到多个结构体或枚举定义,它们之间唯一的区别是它们所持有的值的类型时,您可以通过使用泛型类型来避免代码重复。

在方法中定义

我们可以在结构和枚举上实现方法,也可以在它们的定义中使用泛型类型。现在我们定义Point<T>结构体,并在其上实现了一个名为 x 的方法。

代码语言: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());
}

在这里,我们在 Point<T> 上定义了一个名为 x 的方法,该方法返回对字段 x 中数据的引用。

请注意,我们必须在 impl 之后声明 T,这样我们才能使用 T 来指定我们正在为 Point<T> 类型实现方法。通过在 impl 后面声明 T 作为一个泛型类型,Rust 就能够识别出 Point 中尖括号内的类型是一个泛型类型,而不是一个具体类型。我们本可以选择一个与结构体定义中声明的泛型参数不同的名字,但使用相同的名字是符合惯例的。在声明了泛型类型的 impl 块中编写的方法将被定义在任何实例的类型上,无论最终替代泛型类型的具体类型是什么。

在为类型定义方法时,我们也可以对泛型类型指定约束。例如,我们可以选择只在 Point<f2> 实例上实现方法,而不是在任何泛型类型的 Point<T> 实例上。在下面的示例代码中,我们使用了具体类型 f2,这意味着我们在 impl 后面没有声明任何类型。

代码语言:rust复制
impl Point<f2> {
    fn distance_from_origin(&self) -> f2 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

这段代码意味着 Point<f2> 类型将拥有一个 distance_from_origin 方法;而其他 T 不是 f2 类型的 Point<T> 实例则不会有这个方法定义。这个方法测量我们的点与坐标 (0.0, 0.0) 的点之间的距离,并使用仅对浮点数类型可用的数学运算。

结构体定义中的泛型类型参数并不总是与同一结构体方法签名中使用的泛型类型参数相同。为了使示例更清晰,在下面的代码中,我们使用了 X1Y1 作为 Point 结构体的泛型类型,而 X2Y2 用于 mixup 方法的签名。这个方法创建了一个新的 Point 实例,其 x 值来自 selfPoint(类型为 X1),而 y 值来自传入的 Point(类型为 Y2)。

代码语言:rust复制
struct Point<X1, Y1> {
    x: X1,
    y: Y1,
}

impl<X1, Y1> Point<X1, Y1> {
    fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
        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 p = (p2);

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

main 函数中,我们定义了一个 Point,它的 x 字段是 i2 类型(值为 5),y 字段是 f64 类型(值为 10.4)。变量 p2 是一个 Point 结构体,它的 x 字段是字符串切片类型(值为 "Hello"),y 字段是 char 类型(值为 'c')。用 p2 作为参数调用 p1mixup 方法会得到 ppx 字段将是 i2 类型,因为 x 来自 p1py 字段将是 char 类型,因为 y 来自 p2println! 宏调用将打印出 p.x = 5, p.y = c

这个示例的目的是展示一种情况,即一些泛型参数是随着 impl 一起声明的,而另一些则是随着方法定义一起声明的。在这里,泛型参数 X1Y1impl 之后声明,因为它们与结构体定义相关联。泛型参数 X2Y2fn mixup 之后声明,因为它们只与方法相关。

使用泛型的代码性能

您可能对使用泛型类型参数是否会引入运行时性能开销感到好奇。好消息是,使用泛型类型并不会导致程序运行速度比使用具体类型慢。

Rust 通过在编译时进行单态化(monomorphization)来确保使用泛型类型不会导致程序运行速度变慢。单态化是将泛型代码转换成特定代码的过程,具体来说,就是在编译时根据实际使用的具体类型来填充泛型类型参数。在这个过程中,编译器会查看所有调用泛型代码的地方,并为这些调用生成具体类型的代码。因此,使用泛型时没有运行时开销;代码运行时的效率就如同手写了每个具体类型的代码一样。这个单态化过程正是 Rust 泛型在运行时能够保持极高效率的原因。

#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格

本文地址:http://www.dnpztj.cn/biancheng/1178782.html

相关标签:无
上传时间: 2025-07-21 22:43:57
留言与评论(共有 16 条评论)
本站网友 海通期货下载
15分钟前 发表
因此
本站网友 三星r428
18分钟前 发表
下方的示例代码定义了一个 Point<T> 结构体
本站网友 御泉山庄
21分钟前 发表
作为类型的缩写
本站网友 2月汽车销量
4分钟前 发表
用 p2 作为参数调用 p1 的 mixup 方法会得到 p
本站网友 java之父
21分钟前 发表
Y1
本站网友 广州国际美食节
18分钟前 发表
即一些泛型参数是随着 impl 一起声明的
本站网友 昆明无痛人流
29分钟前 发表
为了在新的单一函数中参数化类型
本站网友 精通开关电源设计
4分钟前 发表
PartialOrd>(list
本站网友 爱爱爱爱到要吐
1分钟前 发表
'q']; let result = largest_char(&char_list); println!("The largest char is {result}"); assert_eq!(*result
本站网友 尚易邮局
19分钟前 发表
&[T]) -> &T {我们可以这样理解这个定义:largest 函数对某种类型 T 是泛型的
本站网友 除疤痕
4分钟前 发表
所以无论可选值是什么类型
本站网友 城镇排水与污水处理条例
23分钟前 发表
该方法返回对字段 x 中数据的引用
本站网友 好友号好好盗
7分钟前 发表
y 字段是 f64 类型(值为 10.4)
本站网友 加盟蛋糕店连锁店
8分钟前 发表
T
本站网友 虾不能和什么一起吃
24分钟前 发表
y