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