news 2026/7/23 19:00:22

【Rust自学】19.2. 高级trait:关联类型、默认泛型参数和运算符重载、完全限定语法、supertrait和newtype

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Rust自学】19.2. 高级trait:关联类型、默认泛型参数和运算符重载、完全限定语法、supertrait和newtype

19.2 高级 trait:关联类型、默认泛型参数和运算符重载、完全限定语法、supertrait 和 newtype

19.2.1 在 trait 定义中使用关联类型来指定占位类型

我们首先在 10.3. trait Pt.1:trait的定义、约束与实现 介绍了 trait,但没有讨论更高级的细节。现在来深入了解。

关联类型是 trait 内部的类型占位符。它可以用于 trait 方法签名中。它用来为某些类型定义 trait,而无需事先知道这些类型是什么。

例如:

pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; }

标准库的Iteratortrait 就是一个带有关联类型的 trait,其定义如上所示。

Item就是关联类型。在迭代过程中,用Item代替实际值的类型,从而把逻辑和具体数据类型分开。你可以在next的返回类型Option<Self::Item>中看到Item

Item是一个类型占位符。它的核心思想与泛型类似,但也有区别:

泛型关联类型
每次实现 trait 时都要指定类型无需指定类型
同一个类型可以用不同的泛型参数多次实现同一个 trait同一个类型不能多次实现同一个 trait

19.2.2 默认泛型类型参数和运算符重载

使用泛型参数时,我们可以给泛型一个默认的具体类型。语法是<PlaceholderType=ConcreteType>。这项技术常用于运算符重载。

虽然 Rust 不允许你创建自己的运算符,也不能重载任意运算符,但你可以通过实现std::ops中列出的那些 trait 来重载某些运算符。

看一个例子:

use std::ops::Add; #[derive(Debug, Copy, Clone, PartialEq)] struct Point { x: i32, y: i32, } impl Add for Point { type Output = Point; fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y, } } } fn main() { assert_eq!( Point { x: 1, y: 0 } + Point { x: 2, y: 3 }, Point { x: 3, y: 3 } ); }
  • 在这个例子中,我们为Point结构体实现了Addtrait,从而重载了+运算符。具体来说,Add中的add函数逐字段相加。
  • main中,我们可以直接用+把两个Point值相加。

Addtrait 的定义如下:

trait Add<Rhs=Self> { type Output; fn add(self, rhs: Rhs) -> Self::Output; }

它使用了默认泛型参数Rhs=Self。这意味着当我们实现Add时,如果不给Rhs指定具体类型,默认类型就是Self。所以上面例子中的RhsPoint

现在再看另一个例子,这次是毫米和米相加:

use std::ops::Add; struct Millimeters(u32); struct Meters(u32); impl Add<Meters> for Millimeters { type Output = Millimeters; fn add(self, other: Meters) -> Millimeters { Millimeters(self.0 + (other.0 * 1000)) } }
  • 这里把MillimetersMeters声明为元组结构体,分别表示毫米和米。
  • 我们为Millimeters实现Add,并显式指定另一个类型是Meters。在add中,我们把存储的毫米值与换算成毫米后的米值相加。

19.2.3 默认泛型参数的主要用例

  • 在不破坏现有代码的前提下扩展类型
  • 允许在大多数用户不需要的特殊情况下进行自定义

19.2.4 使用完全限定语法调用同名方法

直接看例子:

trait Pilot { fn fly(&self); } trait Wizard { fn fly(&self); } struct Human; impl Pilot for Human { fn fly(&self) { println!("This is your captain speaking."); } } impl Wizard for Human { fn fly(&self) { println!("Up!"); } } impl Human { fn fly(&self) { println!("*waving arms furiously*"); } }
  • 我们定义了两个 trait:PilotWizard,各自都有一个fly方法,但没有具体实现。
  • 我们有一个Human结构体。接下来分别为它实现两个 trait,也就是为每个 trait 提供一个fly方法。此外,我们还在结构体自己的impl块中实现了一个fly方法。

此时一共有三个fly方法。如果在main中这样调用:

fn main() { let person = Human; person.fly(); }

运行这段代码会打印*waving arms furiously*,说明 Rust 直接调用了Human上实现的fly方法。

要调用Pilottrait 或Wizardtrait 中的fly,需要用更明确的语法指出我们指的是哪一个fly

fn main() { let person = Human; Pilot::fly(&person); Wizard::fly(&person); person.fly(); }

在方法名前指定 trait 名,可以告诉 Rust 我们想要哪一个fly实现。person.fly()也可以写成Human::fly(&person)

输出:

This is your captain speaking. Up! *waving arms furiously*

然而,不是方法的关联函数没有self参数。当来自不同类型或 trait 的多个方法或关联函数同名时,除非使用完全限定语法,Rust 并不总是知道你指的是哪一个:

trait Animal { fn baby_name() -> String; } struct Dog; impl Dog { fn baby_name() -> String { String::from("Spot") } } impl Animal for Dog { fn baby_name() -> String { String::from("puppy") } } fn main() { println!("A baby dog is called a {}", Dog::baby_name()); }
  • Animaltrait 有一个baby_name函数。Dog是一个结构体,实现了Animaltrait,同时也在自己的impl块中实现了baby_name。所以现在有两个baby_name函数。
  • main中使用了Dog::baby_name(),因此按上面的逻辑,会运行Dog自己的impl块中的baby_name实现,得到Spot

输出:

A baby dog is called a Spot

那么如何调用DogAnimaltrait 的baby_name实现呢?我们试试上一个例子的逻辑:

fn main() { println!("A baby dog is called a {}", Animal::baby_name()); }

输出:

error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:20:43 | 2 | fn baby_name() -> String; | ------------------------- `Animal::baby_name` defined here ... 20 | println!("A baby dog is called a {}", Animal::baby_name()); | ^^^^^^^^^^^^^^^^^^^ cannot call associated function of trait | help: use the fully-qualified path to the only available implementation | 20 | println!("A baby dog is called a {}", <Dog as Animal>::baby_name()); | +++++++ + For more information about this error, try `rustc --explain E0790`. error: could not compile `traits-example` (bin "traits-example") due to 1 previous error

Animaltrait 上的baby_name函数需要知道使用哪个类型的实现,但baby_name本身没有参数,所以 Rust 无法推断指的是哪个类型的实现。

这时就需要完全限定语法。它的形式是:

<Type as Trait>::function(receiver_if_method, next_arg, ...);

这种语法可以在任何调用函数或方法的地方使用,并且可以忽略那些能从其它上下文推断出来的部分。

但只有在 Rust 无法区分你想要哪个具体实现时,才需要这种语法,因为它写起来很麻烦。所以一般来说,除非必要,否则不要使用它。

按这个语法,上面的代码应改为:

fn main() { println!("A baby dog is called a {}", <Dog as Animal>::baby_name()); }

输出:

A baby dog is called a puppy

19.2.5 使用 supertrait 要求额外的 trait 功能

有时我们需要在一个 trait 中使用另一个 trait 的功能,这意味着那个被间接要求的 trait 也必须被实现。那个被间接要求的 trait 就是当前 trait 的 supertrait。

例如:

use std::fmt; trait OutlinePrint: fmt::Display { fn outline_print(&self) { let output = self.to_string(); let len = output.len(); println!("{}", "*".repeat(len + 4)); println!("*{}*", " ".repeat(len + 2)); println!("* {output} *"); println!("*{}*", " ".repeat(len + 2)); println!("{}", "*".repeat(len + 4)); } }

OutlinePrint实际上用来在终端用字符打印一个形状。但在打印时,self必须实现to_string,这意味着self必须实现Displaytrait(to_string来自ToStringtrait,而任何实现了Display的类型都会自动实现ToString)。写法是trait关键字 + trait 名 +:+ supertrait。

假设我们有一个Point结构体,想用OutlinePrintoutline_print方法在终端打印它。因为OutlinePrint要求Display,我们必须同时实现OutlinePrintDisplay,否则会失败:

struct Point { x: i32, y: i32, } use std::fmt; impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } } impl OutlinePrint for Point {}

19.2.6 使用 newtype 模式在外部类型上实现外部 trait

我们已经讨论过孤儿规则:只有当 trait 或类型定义在本地 crate 中时,才能为该类型实现这个 trait。我们可以使用 newtype 模式绕过这条规则,具体做法是用元组结构体在本地构建一个新类型。

例如:

假设我们想为Vec<String>实现Display,但VecDisplay都定义在我们的 crate 之外,所以不能直接为Vec<String>实现。于是我们把这个向量包进自己的元组结构体Wrapper,再为Wrapper实现Display

use std::fmt; struct Wrapper(Vec<String>); impl fmt::Display for Wrapper { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.0.join(", ")) } } fn main() { let w = Wrapper(vec![String::from("hello"), String::from("world")]); println!("w = {w}"); }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/23 18:55:03

【JAVA毕设源码分享】基于springboot大学生就业招聘系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/7/23 18:50:19

【Rust自学】10.1. 提取函数以消除重复代码

10.1 提取函数以消除重复代码 10.1.1. 重复代码 看个例子&#xff1a; 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 {}…

作者头像 李华
网站建设 2026/7/23 18:49:29

腺相关病毒载体构建伯远生物腺相关病毒载体

腺相关病毒载体构建伯远生物腺相关病毒载体 伯远生物是国家级专精特新小巨人企业&#xff0c;国家级重点实验室&#xff0c;牵头多项省部级重大专项&#xff0c;公司科研技术人员500&#xff08;硕博占比40%以上&#xff09;&#xff0c;作为功能基因研究综合性平台&#xff0c…

作者头像 李华
网站建设 2026/7/23 18:47:57

【Rust自学】6.1. 定义枚举

6.1 定义枚举 6.1.1. 什么是枚举&#xff1f; 枚举允许我们通过列举所有可能的值来定义一个类型。这与其他编程语言中的枚举类似&#xff0c;但 Rust 的枚举更加灵活和强大&#xff0c;因为它们可以关联数据和方法&#xff0c;类似于其他语言中的类或结构体。 6.1.2. 定义枚…

作者头像 李华
网站建设 2026/7/23 18:46:27

【Rust自学】3.2. 数据类型:标量类型

3.2 数据类型&#xff1a;标量类型 3.2.0. 写在正文之前 欢迎来到Rust自学的第三章&#xff0c;一共有6个小节&#xff0c;分别是: - 变量与可变性 - 数据类型&#xff1a;标量类型&#xff08;本文&#xff09; - 数据类型&#xff1a;复合类型 - 函数和注释 - 控制流&#…

作者头像 李华
网站建设 2026/7/23 18:45:27

基于ddddocr与Hu矩的验证码识别技术实践

1. 项目背景与需求分析验证码识别一直是自动化测试和爬虫开发中的难点问题。某象验证码作为业内知名的验证码服务提供商&#xff0c;其差异点击验证码通过动态生成干扰元素和随机位置点击点&#xff0c;给传统OCR识别带来了巨大挑战。这类验证码通常包含以下特征&#xff1a;随…

作者头像 李华