将智能指针视为常规引用Deref
特性
实施Deref
trait 允许您自定义 dereference 运算符的行为(不要与 multiplication 或 glob 混淆
运算符)。通过实施*
Deref
这样,智能指针就可以
被视为常规引用,您可以编写对
引用,并将该代码与智能指针一起使用。
首先,让我们看看 dereference 运算符如何处理常规引用。
然后我们将尝试定义一个行为类似于Box<T>
,看看为什么
dereference 运算符的工作方式与我们新定义的
类型。我们将探讨如何实现Deref
trait 使
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
因为
我们希望我们的类型包含任何类型的值。这MyBox
type 是一个元组结构
具有一个类型为T
.这MyBox::new
function 采用
类型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的实现。这Deref
trait 中,提供
,要求我们实现一个名为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 为Deref
trait 一起使用。关联类型是声明
generic 参数,但您现在无需担心它们;我们将涵盖
它们在第 19 章中有更详细的解释。
我们填写deref
method 替换为&self.0
所以deref
返回
引用我们要使用运算符访问的值;回想一下“使用不带命名字段的元组结构创建不同的
Types“部分,其中*
.0
访问
元组结构中的第一个值。这main
示例 15-9 中的
调用*
MyBox<T>
value 现在编译,并且断言通过!
如果没有Deref
trait 时,编译器只能取消引用。
这&
deref
method 使编译器能够采用任何类型的值
实现Deref
并调用deref
方法获取引用,该引用
它知道如何取消引用。&
当我们进入*y
在示例 15-9 中,Rust 在幕后实际上运行了这个
法典:
*(y.deref())
Rust 将运算符替换为对*
deref
方法,然后是
plain dereference,因此我们不必考虑是否需要
调用deref
方法。这个 Rust 功能让我们编写代码,将
无论我们有一个常规的引用还是一个实现Deref
.
原因deref
方法返回对值的引用,并且
括号外的普通取消引用*(y.deref())
仍然是必需的,
与所有权制度有关。如果deref
method 返回值
而不是对值的引用,而是将该值从self
.我们不想拥有内在价值的所有权MyBox<T>
在
在这种情况下,或者在大多数情况下,我们使用 dereference 运算符。
请注意,运算符将替换为对*
deref
method 和
然后只调用一次运算符,每次我们在代码中使用 a 时。
因为运算符的替换不是无限递归的,所以我们
最终得到 data 类型的*
*
*
i32
,它与5
在assert_eq!
在
示例 15-9.
使用函数和方法的隐式 Deref 强制转换
Deref 强制将引用转换为实现Deref
trait 转换为对另一种类型的引用。例如,deref 强制转换&String
自&str
因为String
实现Deref
trait 的 Trait 中,它
返回&str
.Deref 强制转换是 Rust 对参数执行的一种便利
函数和方法,并且仅适用于实现Deref
特性。当我们将引用传递给特定类型的
value 作为与参数不匹配的函数或方法的参数
键入函数或方法定义。对deref
method 将我们提供的类型转换为参数所需的类型。
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 自动为我们处理这些转换。
当Deref
trait 定义时,Rust 将分析
类型和用途Deref::deref
根据需要多次获取对
match 参数的类型。该Deref::deref
需要
inserted 在编译时解析,因此采用
Deref 强制的优势!
Deref Coercion 如何与可变互
类似于您使用Deref
trait 覆盖 operator on
immutable 引用,您可以使用*
DerefMut
trait 覆盖可变引用上的运算符。*
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/)为准