高级特征
我们首先在“特征:定义共享 行为“部分 10 中,但我们没有讨论更高级的细节。现在您知道更多 关于 Rust,我们可以深入了解细节。
使用关联类型在 Trait Definitions 中指定占位符类型
关联类型将类型占位符与 trait 连接起来,以便 trait 方法定义可以在其签名中使用这些占位符类型。这 trait 的 implementor 将指定要使用的具体类型,而不是 placeholder 类型。这样,我们就可以定义一个 trait 使用某些类型,但不需要确切知道这些类型是什么 ,直到实现 trait。
我们在本章中描述了大多数高级功能,因为很少 需要。关联类型介于两者之间:它们很少使用 比本书其余部分解释的功能更常见,但比许多 本章中讨论的其他功能。
具有关联类型的 trait 的一个示例是Iterator
trait 的
standard 库提供。关联的类型名为Item
并替身
对于值的类型,实现Iterator
trait 是
迭代。的定义Iterator
trait 如 清单 所示
19-12.
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
示例 19-12:Iterator
特性
具有关联类型Item
类型Item
是一个占位符,而next
method 的定义显示
它将返回Option<Self::Item>
.的Iterator
trait 将指定Item
和next
方法将返回一个Option
包含该具体类型的值。
关联类型可能看起来与泛型的概念类似,因为
后者允许我们定义一个函数,而无需指定它可以是什么类型
处理。为了检查这两个概念之间的区别,我们将查看一个
实现Iterator
trait 的Counter
指定
这Item
type 为u32
:
文件名: src/lib.rs
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
// --snip--
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
此语法似乎与泛型的语法相当。那么为什么不直接定义Iterator
trait 替换为泛型,如示例 19-13 所示?
pub trait Iterator<T> {
fn next(&mut self) -> Option<T>;
}
示例 19-13:Iterator
使用泛型的 trait
区别在于,当使用泛型时,如示例 19-13 所示,我们必须
注释每个 implementation中的类型;因为我们也可以实现Iterator<String> for Counter
或任何其他类型,我们可以有多个
的实现Iterator
为Counter
.换句话说,当特征具有
generic 参数,它可以多次为一个类型实现,更改
每次泛型类型参数的具体类型。当我们使用next
method 开启Counter
,我们必须为
指示Iterator
我们想使用。
使用关联类型,我们不需要注释类型,因为我们不能
多次在一个类型上实现一个 trait。在示例 19-12 中,使用
定义,我们只能选择Item
将是一次,因为只能有一个impl Iterator for Counter
.
我们不必指定我们想要一个u32
无处不在的价值观
我们称之为next
上Counter
.
关联类型也成为 trait 契约的一部分:的 trait 必须提供一个 type 来代替关联的 type placeholder。 关联类型通常具有描述如何使用类型的名称。 在 API 文档中记录关联的类型是一种很好的做法。
默认泛型类型参数和运算符重载
当我们使用泛型类型参数时,我们可以为
泛型类型。这消除了 trait 的实现者对
如果默认类型有效,请指定具体类型。指定默认类型
当使用<PlaceholderType=ConcreteType>
语法。
此技术有用的一个很好的示例是使用运算符
重载,其中自定义运算符(如 )
在特定情况下。+
Rust 不允许你创建自己的运算符或重载任意
运营商。但是,您可以重载列出的作和相应的特征
在std::ops
通过实施与 Operator 关联的特征。为
例如,在示例 19-14 中,我们重载了 operator 以添加两个+
Point
实例一起。我们通过实现Add
traitPoint
结构:
文件名: src/main.rs
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 } ); }
示例 19-14:实现Add
trait 重载
的运算符+
Point
实例
这add
方法添加x
值为 2Point
实例和y
值为 2Point
实例以创建新的Point
.这Add
trait 具有
关联的类型Output
,它决定了从add
方法。
此代码中的默认泛型类型位于Add
特性。这是它的
定义:
#![allow(unused)] fn main() { trait Add<Rhs=Self> { type Output; fn add(self, rhs: Rhs) -> Self::Output; } }
这段代码应该看起来大致很熟悉:一个具有一个方法的 trait 和一个
associated 类型。新部分是Rhs=Self
:此语法称为 default
类型参数。这Rhs
泛型类型参数(“right hand”的缩写
side“) 定义rhs
参数中的add
方法。如果我们不这样做
指定具体类型Rhs
当我们实现Add
trait 时,类型
之Rhs
将默认为Self
,这将是我们正在实现的类型Add
上。
当我们实施Add
为Point
,我们使用了Rhs
因为我们
想加两个Point
实例。让我们看一个实现
这Add
trait 中,我们想要自定义Rhs
键入,而不是使用
违约。
我们有两个结构体,Millimeters
和Meters
,将值保存在不同的
单位。将现有类型放在另一个结构体中的这种薄包装称为 newtype 模式,我们在“使用 newtype
Pattern to Implement External traits on External Types“部分。我们想将以毫米为单位的值与以米为单位的值相加,并得到
实施Add
正确进行转换。我们可以实施Add
为Millimeters
跟Meters
作为Rhs
,如示例 19-15 所示。
文件名: src/lib.rs
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))
}
}
示例 19-15:实现Add
trait 开启Millimeters
添加Millimeters
自Meters
添加Millimeters
和Meters
,我们指定impl Add<Meters>
要设置
的值Rhs
type 参数,而不是使用默认的Self
.
您将以两种主要方式使用默认类型参数:
- 在不破坏现有代码的情况下扩展类型
- 为了允许在特定情况下进行自定义,大多数用户不需要
标准库的Add
trait 是第二个目的的一个例子:
通常,您将添加两个 like 类型,但Add
trait 提供了
除此之外进行定制。在Add
特性
定义意味着您不必指定大多数
时间。换句话说,不需要一些实现样板,使
使用 trait 更容易。
第一个目的与第二个目的类似,但方向相反:如果要添加 type 参数添加到现有 trait 中,您可以为其指定默认值以允许 在不破坏现有 implementation code 的 implementation code 中。
消除歧义的完全限定语法:调用具有相同名称的方法
Rust 中没有任何内容可以阻止 trait 具有与 another trait 的方法,Rust 也不会阻止你实现这两个 trait 在一种类型上。也可以使用 与 traits 中的方法同名。
当调用具有相同名称的方法时,你需要告诉 Rust 你是哪一个
想要使用。考虑示例 19-16 中的代码,我们定义了两个 trait,Pilot
和Wizard
,这两个方法都有一个名为fly
.然后,我们实施
类型上的两个 traitHuman
已经有一个名为fly
实现
在上面。每fly
方法执行不同的作。
文件名: src/main.rs
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*"); } } fn main() {}
示例 19-16:定义了两个 trait 以具有fly
方法,并在Human
type 和fly
method 为
实施日期Human
径直
当我们调用fly
在Human
,编译器默认调用
直接在类型上实现的方法,如示例 19-17 所示。
文件名: src/main.rs
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*"); } } fn main() { let person = Human; person.fly(); }
示例 19-17:调用fly
在Human
运行此代码将打印*waving arms furiously*
,显示 Rust
称为fly
方法实现于Human
径直。
要调用fly
方法中的Pilot
trait 或Wizard
特性
我们需要使用更明确的语法来指定哪个fly
方法。
示例 19-18 演示了此语法。
文件名: src/main.rs
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*"); } } fn main() { let person = Human; Pilot::fly(&person); Wizard::fly(&person); person.fly(); }
示例 19-18:指定哪个 trait 的fly
方法 we
想要调用
在方法名称之前指定 trait name 会向 Rust 阐明哪个
实现fly
我们想打电话。我们也可以编写Human::fly(&person)
,它相当于person.fly()
我们使用的
在示例 19-18 中,但如果我们不需要的话,写起来会有点长
消除歧义。
运行此代码将打印以下内容:
$ cargo run
Compiling traits-example v0.1.0 (file:///projects/traits-example)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.46s
Running `target/debug/traits-example`
This is your captain speaking.
Up!
*waving arms furiously*
因为fly
method 采用self
parameter 参数,如果我们有两个类型,
都实现了一个 trait,Rust 可以找出
trait 来根据self
.
但是,不是方法的关联函数没有self
参数。当有多个类型或特征定义非方法
函数具有相同的函数名称,Rust 并不总是知道你是哪种类型
表示,除非你使用完全限定的语法。例如,在示例 19-19 中,我们
为想要将所有婴儿狗命名为 Spot 的动物收容所创建一个特征。
我们制作一个Animal
trait 替换为关联的非方法函数baby_name
.
这Animal
trait 为 struct 实现Dog
,我们还
提供关联的非方法函数baby_name
径直。
文件名: src/main.rs
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()); }
示例 19-19:具有关联函数和 type 替换为同名的关联函数,该函数还实现 特性
我们在baby_name
相关
在Dog
.这Dog
type 也实现了 traitAnimal
,它描述了所有动物都具有的特征。婴儿犬是
称为 puppies,这体现在实现Animal
trait 开启Dog
在baby_name
函数与Animal
特性。
在main
,我们调用Dog::baby_name
函数调用关联的
函数定义于Dog
径直。此代码打印以下内容:
$ cargo run
Compiling traits-example v0.1.0 (file:///projects/traits-example)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.54s
Running `target/debug/traits-example`
A baby dog is called a Spot
这个输出不是我们想要的。我们想调用baby_name
函数
是Animal
trait 实现的Dog
所以代码打印出来A baby dog is called a puppy
.指定 trait 名称的技术
我们在示例 19-18 中使用的在这里没有帮助;如果我们更改main
到
示例 19-20,我们将得到一个编译错误。
文件名: src/main.rs
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 {}", Animal::baby_name());
}
示例 19-20:尝试调用baby_name
函数Animal
trait 的 intent 中,但 Rust 不知道该用哪个
用
因为Animal::baby_name
没有self
参数,并且可能存在
实现Animal
trait 中,Rust 无法弄清楚是哪个
实现Animal::baby_name
我们想要。我们将收到这个编译器错误:
$ cargo run
Compiling traits-example v0.1.0 (file:///projects/traits-example)
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
为了消除歧义并告诉 Rust 我们想使用Animal
为Dog
与Animal
对于其他
type 时,我们需要使用完全限定的语法。示例 19-21 演示了如何
使用完全限定的语法。
文件名: src/main.rs
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 as Animal>::baby_name()); }
示例 19-21:使用完全限定语法指定
我们想要调用baby_name
函数Animal
trait 设置为
实施日期Dog
我们在尖括号内为 Rust 提供了一个类型注释,该
表示我们想要调用baby_name
方法从Animal
trait 设置为
实施日期Dog
通过表示我们想要处理Dog
type 作为Animal
对于此函数调用。这段代码现在将打印我们想要的内容:
$ cargo run
Compiling traits-example v0.1.0 (file:///projects/traits-example)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
Running `target/debug/traits-example`
A baby dog is called a puppy
通常,完全限定语法定义如下:
<Type as Trait>::function(receiver_if_method, next_arg, ...);
对于不是方法的关联函数,不会有receiver
:
只有其他参数的列表。您可以使用 Fully qualified
语法。但是,您可以
省略 Rust 可以从其他信息中找出的语法的任何部分
在程序中。您只需在以下情况下使用这种更详细的语法
有多个实现使用相同的名称,Rust 需要帮助
来确定要调用的实现。
使用 supertrait 要求一个特征在另一个特征中的功能
有时,您可能会编写一个依赖于另一个 trait 的 trait 定义: 对于要实现第一个 trait 的类型,您希望要求该类型也 实现第二个 trait。您这样做是为了让您的特征定义可以 利用第二个特征的关联项。特征 您的特征 定义 is 依赖于 称为 你的 trait 的 supertrait。
例如,假设我们想创建一个OutlinePrint
trait 中具有outline_print
方法,该方法将打印一个格式化的给定值,以便它是
以星号装裱。也就是说,给定一个Point
结构体实现
标准库特征Display
以产生(x, y)
,当我们调用outline_print
在Point
实例,该实例具有1
为x
和3
为y
它
应打印以下内容:
**********
* *
* (1, 3) *
* *
**********
在outline_print
方法,我们希望使用Display
trait 的功能。因此,我们需要指定OutlinePrint
trait 仅适用于同时实现Display
和
提供的功能OutlinePrint
需要。我们可以在
trait 定义OutlinePrint: Display
.该技术是
类似于添加绑定到 trait 的 trait。示例 19-22 显示了一个
实现OutlinePrint
特性。
文件名: src/main.rs
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)); } } fn main() {}
示例 19-22:实现OutlinePrint
trait
需要Display
因为我们已经指定了OutlinePrint
需要Display
trait 的
可以使用to_string
为任何类型的自动实现的函数
实现Display
.如果我们尝试使用to_string
而不添加
colon 并指定Display
trait 的 trait 中,我们会得到一个
错误地表示没有名为to_string
找到&Self
在
当前范围。
让我们看看当我们尝试实现OutlinePrint
在类型上
不实现Display
,例如Point
结构:
文件名: src/main.rs
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));
}
}
struct Point {
x: i32,
y: i32,
}
impl OutlinePrint for Point {}
fn main() {
let p = Point { x: 1, y: 3 };
p.outline_print();
}
我们收到一个错误,指出Display
是必需的,但未实现:
$ cargo run
Compiling traits-example v0.1.0 (file:///projects/traits-example)
error[E0277]: `Point` doesn't implement `std::fmt::Display`
--> src/main.rs:20:23
|
20 | impl OutlinePrint for Point {}
| ^^^^^ `Point` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `Point`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
note: required by a bound in `OutlinePrint`
--> src/main.rs:3:21
|
3 | trait OutlinePrint: fmt::Display {
| ^^^^^^^^^^^^ required by this bound in `OutlinePrint`
error[E0277]: `Point` doesn't implement `std::fmt::Display`
--> src/main.rs:24:7
|
24 | p.outline_print();
| ^^^^^^^^^^^^^ `Point` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `Point`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
note: required by a bound in `OutlinePrint::outline_print`
--> src/main.rs:3:21
|
3 | trait OutlinePrint: fmt::Display {
| ^^^^^^^^^^^^ required by this bound in `OutlinePrint::outline_print`
4 | fn outline_print(&self) {
| ------------- required by a bound in this associated function
For more information about this error, try `rustc --explain E0277`.
error: could not compile `traits-example` (bin "traits-example") due to 2 previous errors
为了解决这个问题,我们实施了Display
上Point
并满足OutlinePrint
requires,如下所示:
文件名: src/main.rs
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)); } } struct Point { x: i32, y: i32, } impl OutlinePrint for Point {} use std::fmt; impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } } fn main() { let p = Point { x: 1, y: 3 }; p.outline_print(); }
然后实现OutlinePrint
trait 开启Point
将编译
成功,我们可以调用outline_print
在Point
要显示的实例
它在星号的轮廓内。
使用 newtype 模式在外部类型上实现 external trait
在第 10 章的 “在 Type“部分,我们提到了 orphan 规则,它规定我们只允许在类型上实现 trait,如果 trait 或 type 都是我们 crate 的本地。有可能获得 使用 newType 模式绕过此限制,这涉及创建一个 new 类型。(我们在“使用 Tuple 没有命名字段的结构体来创建不同类型的“部分。元组结构将有一个字段,并且是一个 thin 包装器。然后,包装器 type 是我们的 crate 的本地类型,我们可以在 wrapper 上实现 trait。Newtype 是一个源自 Haskell 编程语言的术语。 使用此模式不会对运行时性能造成影响,并且包装器 type 在编译时被省略。
例如,假设我们想要实现Display
上Vec<T>
,其中
孤立规则阻止我们直接执行作,因为Display
trait 和Vec<T>
type 在我们的 crate 之外定义。我们可以制作一个Wrapper
结构
它包含一个Vec<T>
;然后我们就可以实施Display
上Wrapper
并使用Vec<T>
值,如示例 19-23 所示。
文件名: src/main.rs
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}"); }
示例 19-23:创建一个Wrapper
键入周围Vec<String>
实施Display
的实现Display
使用self.0
访问内部Vec<T>
,
因为Wrapper
是一个元组结构,Vec<T>
是
元。然后我们可以使用Display
trait 开启Wrapper
.
使用这种技术的缺点是Wrapper
是一个新类型,因此它
没有它所持有的价值的方法。我们必须实施
的所有方法Vec<T>
直接打开Wrapper
这样,方法
delegate toself.0
,这将允许我们将Wrapper
与Vec<T>
.如果我们希望新类型具有内部类型所具有的所有方法,
实现Deref
trait 中(在第 15 章的“治疗 Smart
指针(如常规引用)中带有Deref
性状”部分)在Wrapper
返回
内部类型将是一个解决方案。如果我们不想让Wrapper
type to have
inner 类型的所有方法,例如,要限制Wrapper
type 的
行为 — 我们只需要手动实现我们想要的方法。
即使不涉及 trait,这种 newtype 模式也很有用。让我们 切换焦点并查看一些与 Rust 的类型系统交互的高级方法。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准