Deref
特性
在上一练习中,你其实没做太多工作,对吧?将
#![allow(unused)] fn main() { impl Ticket { pub fn title(&self) -> &String { &self.title } } }
修改为
#![allow(unused)] fn main() { impl Ticket { pub fn title(&self) -> &str { &self.title } } }
就是为了让代码编译通过并使测试成功。不过,你的脑海中或许会响起警钟。
看似不应行得通,却偏偏可行
让我们回顾一下事实:
self.title
是一个String
- 因此,
&self.title
是一个&String
- 修改后的
title
方法输出的是&str
你可能会期待编译错误,对吧?比如“预期 &String,发现 &str”之类的。然而,它就这么正常工作了。为何如此**?
Deref
特性来救援
Deref
特性是名为解引用强制(deref 强制)这一语言特性的背后机制。该特性在标准库的 std::ops
模块中定义:
#![allow(unused)] fn main() { // 我暂时简化了定义。 // 后面我们会看到完整定义。 pub trait Deref { type Target; fn deref(&self) -> &Self::Target; } }
type Target
是一个关联类型,它是实现特质时必须指定的具体类型占位符。
解引用强制
通过为类型 T
实现 Deref<Target = U>
,你实际上是告诉编译器 &T
和 &U
在某种程度上可以互换。具体而言,你会得到以下行为:
- 对
T
的引用会被隐式转换为对U
的引用(即&T
变为&U
) - 你可以对
&T
调用所有在U
上定义的接受&self
作为输入的方法。
关于解引用操作符 *
还有一点,但我们目前不需要了解(如果好奇可以查阅std
的文档)。
String
实现了 Deref
String
通过 Target = str
实现了 Deref
:
#![allow(unused)] fn main() { impl Deref for String { type Target = str; fn deref(&self) -> &str { // [...] } } }
得益于这个实现和解引用强制,当需要时 &String
会自动转换为 &str
。
不要慎用解引用强制
解引用强制是一个强大的特性,但也可能导致混淆。自动类型转换会使代码更难读且难以理解。如果同一名称的方法既定义在 T
上也在 U
上,到底会调用哪个?
在课程的后续部分,我们将探讨解引用强制最安全的使用场景:智能指针。
参考资料
- 本节的练习位于
exercises/04_traits/06_deref