将智能指针视为常规引用Deref特性
实施Dereftrait 允许您自定义 dereference 运算符的行为(不要与 multiplication 或 glob 混淆
运算符)。通过实施*Deref这样,智能指针就可以
被视为常规引用,您可以编写对
引用,并将该代码与智能指针一起使用。
首先,让我们看看 dereference 运算符如何处理常规引用。
然后我们将尝试定义一个行为类似于Box<T>,看看为什么
dereference 运算符的工作方式与我们新定义的
类型。我们将探讨如何实现Dereftrait 使
Smart Pointers 的工作方式类似于 References。然后我们来看看
Rust 的 deref 强制功能以及它如何让我们使用任一引用
或智能指针。
注意: 这两者之间有一个很大的区别MyBox<T>type 我们即将
build 和真实的Box<T>:我们的版本不会将其数据存储在堆上。
我们将此示例重点介绍Deref,即数据的实际存储位置
不如指针式行为重要。
将指针指向值
常规引用是一种指针,指针的一种理解方式是
作为箭头,指向存储在其他位置的 Value。在示例 15-6 中,我们创建了一个
对i32值,然后使用 dereference 运算符跟在
对值的引用:
文件名: src/main.rs
fn main() { let x = 5; let y = &x; assert_eq!(5, x); assert_eq!(5, *y); }
示例 15-6:使用 dereference 运算符跟随
对i32价值
变量x持有i32价值5.我们设置y等于对x.我们可以断言x等于5.但是,如果我们想让
对y,我们必须使用*y以遵循参考
)的值(因此取消引用),以便编译器可以比较
实际值。一旦我们取消引用y,我们可以访问 integer 值y指向我们可以比较的5.
如果我们尝试编写assert_eq!(5, y);相反,我们会得到这个编译
错误:
$ cargo run
Compiling deref-example v0.1.0 (file:///projects/deref-example)
error[E0277]: can't compare `{integer}` with `&{integer}`
--> src/main.rs:6:5
|
6 | assert_eq!(5, y);
| ^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}`
|
= help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}`
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0277`.
error: could not compile `deref-example` (bin "deref-example") due to 1 previous error
不允许将数字和引用与数字进行比较,因为它们是 不同的类型。我们必须使用 dereference 运算符来遵循引用 设置为它所指向的值。
用Box<T>Like a Reference
我们可以重写示例 15-6 中的代码,使用Box<T>而不是
参考;在Box<T>在示例 15-7 中
函数的运行方式与在
示例 15-6:
文件名: src/main.rs
fn main() { let x = 5; let y = Box::new(x); assert_eq!(5, x); assert_eq!(5, *y); }
示例 15-7:在Box<i32>
示例 15-7 和示例 15-6 的主要区别在于,这里我们设置了y作为Box<T>指向复制的值x而
而不是指向x.在最后一个断言中,我们可以
使用 dereference 运算符跟随Box<T>在同一
我们什么时候做的y是一个参考。接下来,我们将探讨什么是特别的
大约Box<T>这使我们能够通过定义我们的
own 类型。
定义我们自己的智能指针
让我们构建一个类似于Box<T>type 由
标准库来体验智能指针的行为与
引用。然后,我们将了解如何添加使用
dereference 运算符。
这Box<T>type 最终定义为具有一个元素的元组结构,因此
示例 15-8 定义了一个MyBox<T>键入。我们还将定义一个new函数来匹配new函数定义于Box<T>.
文件名: src/main.rs
struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } fn main() {}
示例 15-8:定义一个MyBox<T>类型
我们定义了一个名为MyBox并声明一个泛型参数T因为
我们希望我们的类型包含任何类型的值。这MyBoxtype 是一个元组结构
具有一个类型为T.这MyBox::newfunction 采用
类型T并返回一个MyBox实例,该实例保存传入的值。
让我们尝试添加main示例 15-7 中的函数到示例 15-8 和
将其更改为使用MyBox<T>type 而不是Box<T>.这
示例 15-9 中的代码无法编译,因为 Rust 不知道如何取消引用MyBox.
文件名: src/main.rs
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
示例 15-9:尝试使用MyBox<T>在同一
我们使用引用和Box<T>
这是生成的编译错误:
$ cargo run
Compiling deref-example v0.1.0 (file:///projects/deref-example)
error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
--> src/main.rs:14:19
|
14 | assert_eq!(5, *y);
| ^^
For more information about this error, try `rustc --explain E0614`.
error: could not compile `deref-example` (bin "deref-example") due to 1 previous error
我们MyBox<T>type 不能被取消引用,因为我们还没有实现它
能力在我们的类型上。要启用运算符的取消引用,我们
实现*Deref特性。
通过实现Deref特性
如第 10 章的 “在类型上实现 trait” 部分所讨论的,要实现 trait,我们需要提供
trait 的 required methods的实现。这Dereftrait 中,提供
,要求我们实现一个名为deref那
借self并返回对内部数据的引用。示例 15-10
包含Deref添加到MyBox:
文件名: src/main.rs
use std::ops::Deref; impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } fn main() { let x = 5; let y = MyBox::new(x); assert_eq!(5, x); assert_eq!(5, *y); }
示例 15-10:实现Deref上MyBox<T>
这type Target = T;syntax 为Dereftrait 一起使用。关联类型是声明
generic 参数,但您现在无需担心它们;我们将涵盖
它们在第 19 章中有更详细的解释。
我们填写derefmethod 替换为&self.0所以deref返回
引用我们要使用运算符访问的值;回想一下“使用不带命名字段的元组结构创建不同的
Types“部分,其中*.0访问
元组结构中的第一个值。这main示例 15-9 中的
调用*MyBox<T>value 现在编译,并且断言通过!
如果没有Dereftrait 时,编译器只能取消引用。
这&derefmethod 使编译器能够采用任何类型的值
实现Deref并调用deref方法获取引用,该引用
它知道如何取消引用。&
当我们进入*y在示例 15-9 中,Rust 在幕后实际上运行了这个
法典:
*(y.deref())
Rust 将运算符替换为对*deref方法,然后是
plain dereference,因此我们不必考虑是否需要
调用deref方法。这个 Rust 功能让我们编写代码,将
无论我们有一个常规的引用还是一个实现Deref.
原因deref方法返回对值的引用,并且
括号外的普通取消引用*(y.deref())仍然是必需的,
与所有权制度有关。如果derefmethod 返回值
而不是对值的引用,而是将该值从self.我们不想拥有内在价值的所有权MyBox<T>在
在这种情况下,或者在大多数情况下,我们使用 dereference 运算符。
请注意,运算符将替换为对*derefmethod 和
然后只调用一次运算符,每次我们在代码中使用 a 时。
因为运算符的替换不是无限递归的,所以我们
最终得到 data 类型的***i32,它与5在assert_eq!在
示例 15-9.
使用函数和方法的隐式 Deref 强制转换
Deref 强制将引用转换为实现Dereftrait 转换为对另一种类型的引用。例如,deref 强制转换&String自&str因为String实现Dereftrait 的 Trait 中,它
返回&str.Deref 强制转换是 Rust 对参数执行的一种便利
函数和方法,并且仅适用于实现Deref特性。当我们将引用传递给特定类型的
value 作为与参数不匹配的函数或方法的参数
键入函数或方法定义。对derefmethod 将我们提供的类型转换为参数所需的类型。
Deref 强制转换被添加到 Rust 中,以便程序员编写函数和
方法调用不需要添加尽可能多的显式引用和取消引用
with 和 .deref 强制功能还允许我们编写更多代码
可以用于引用或智能指针。&*
要查看 deref 强制转换的实际效果,让我们使用MyBox<T>type
示例 15-8 以及Deref我们在 清单 中添加的
15-10. 示例 15-11 显示了具有字符串 slice 的函数的定义
参数:
文件名: src/main.rs
fn hello(name: &str) { println!("Hello, {name}!"); } fn main() {}
示例 15-11:一个hello函数,该函数的参数为name的类型&str
我们可以调用hello函数,例如hello("Rust");例如。Deref 强制使得调用hello引用 type 为MyBox<String>,如示例 15-12 所示:
文件名: src/main.rs
use std::ops::Deref; impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { &self.0 } } struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } fn hello(name: &str) { println!("Hello, {name}!"); } fn main() { let m = MyBox::new(String::from("Rust")); hello(&m); }
示例 15-12:调用hello引用MyBox<String>值,由于 deref 强制转换而有效
这里我们调用hello函数,参数为&m,它是一个
对MyBox<String>价值。因为我们实现了Deref特性
上MyBox<T>在示例 15-10 中,Rust 可以&MyBox<String>到&String通过调用deref.标准库提供了Deref上String返回一个字符串 slice,这在 API 文档中
为Deref.Rust 调用deref再次转动&String到&str哪
匹配hello函数的定义。
如果 Rust 没有实现 deref 强制转换,我们将不得不将代码写入
示例 15-13 而不是示例 15-12 中的代码来调用hello具有值
的类型&MyBox<String>.
文件名: src/main.rs
use std::ops::Deref; impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { &self.0 } } struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } fn hello(name: &str) { println!("Hello, {name}!"); } fn main() { let m = MyBox::new(String::from("Rust")); hello(&(*m)[..]); }
示例 15-13: 如果 Rust 没有 deref 强制
这(*m)取消引用MyBox<String>转换为String.然后 和&[..]获取String等于整个字符串
匹配hello.这段没有 deref 强制转换的代码更难
阅读、写入和理解所有这些符号。Deref 强制
允许 Rust 自动为我们处理这些转换。
当Dereftrait 定义时,Rust 将分析
类型和用途Deref::deref根据需要多次获取对
match 参数的类型。该Deref::deref需要
inserted 在编译时解析,因此采用
Deref 强制的优势!
Deref Coercion 如何与可变互
类似于您使用Dereftrait 覆盖 operator on
immutable 引用,您可以使用*DerefMuttrait 覆盖可变引用上的运算符。*
Rust 在三个 例:
- 从
&T自&U什么时候T: Deref<Target=U> - 从
&mut T自&mut U什么时候T: DerefMut<Target=U> - 从
&mut T自&U什么时候T: Deref<Target=U>
前两种情况彼此相同,只是第二种情况
实现可变性。第一种情况表明,如果你有一个&T和T实现Deref到某种类型U,您可以获得&U透明。这
第二种情况表明,可变引用会发生相同的 deref 强制转换。
第三种情况更棘手: Rust 还会强制一个可变引用指向 immutable 的。但反之是不可能的:不可变引用将 永远不要强制可变引用。由于借款规则的原因,如果您有 一个可变引用,则该可变引用必须是对该 data 的 (否则,程序将无法编译)。转换一个 mutable 对一个不可变引用的引用永远不会违反借用规则。 将不可变引用转换为可变引用需要 初始不可变引用是该数据的唯一不可变引用,但 借款规则并不能保证这一点。因此,Rust 无法将 假设将不可变引用转换为可变引用是 可能。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准