From bcec3e932bbe8f9798bb7538c4d58dfa7a6085fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A4=E7=8B=90=E4=B8=80=E5=86=B2?= <43949039+anonymousGiga@users.noreply.github.com> Date: Thu, 18 May 2023 09:47:48 +0800 Subject: [PATCH] Create chapter_3_16_3.md --- src/chapter_3/chapter_3_16_3.md | 137 ++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 src/chapter_3/chapter_3_16_3.md diff --git a/src/chapter_3/chapter_3_16_3.md b/src/chapter_3/chapter_3_16_3.md new file mode 100644 index 0000000..effb60e --- /dev/null +++ b/src/chapter_3/chapter_3_16_3.md @@ -0,0 +1,137 @@ +# 3.16.3 Deref trait +## 1. 通过*使用引用背后的值 + +常规引用是一个指针类型,包含了目标数据存储的内存地址。对常规引用使用 * ,可以通过解引用的方式获取到内存地址对应的数据值,示例如下: +```Rust +fn main() { + let x = 5; + let y = &x; + + assert_eq!(5, x); + // assert_eq!(5, y); //编译错误,必须通过*才能使用y引用的值 + assert_eq!(5, *y); +} +``` + +## 2. 通过*使用智能指针背后的值 +对于智能指针,也可以通过*使用其背后的值,示例如下: +```Rust +fn main() { + let x = 5; + let y = Box::new(x); + assert_eq!(5, x); + assert_eq!(5, *y); +} +``` + +## 3. 使用Deref + +实现Deref trait允许我们重载解引用运算符*。通过为类型实现Deref trait,类型可以被当做常规引用来对待。简单来说,如果类型A实现了Deref trait,那么就可以写如下代码: +```Rust + let a: A = A::new(); + let b = &a; + let c = *b; //对A实现了Deref trait,所以可以对A类型解引用 +``` + +下面的代码定义一个MyBox类型,并为其实现Deref trait: +```Rust +use std::ops::Deref; +struct MyBox(T); +impl MyBox { + fn new(x: T) -> MyBox { + MyBox(x) + } +} +impl Deref for MyBox { + //为MyBox实现Deref trait + type Target = T; + fn deref(&self) -> &T { + //注意:此处返回值是引用,因为一般并不希望解引用获取MyBox内部值的所有权 + &self.0 + } +} +fn main() { + let x = 5; + let y = MyBox::new(x); + assert_eq!(5, x); + assert_eq!(5, *y); //实现Deref trait后即可解引用,使用*y实际等价于 *(y.deref()) +} +``` + +## 4. 函数和方法的隐式Deref强制转换 +对于函数和方法的传参,Rust 提供了隐式转换:Deref 转换。若一个类型实现了 Deref 特征,那它的引用在传给函数或方法时,会根据参数签名来决定是否进行隐式的 Deref 转换,例如: +```Rust +use std::ops::Deref; +struct MyString(String); +impl Deref for MyString { // MyString类型实现了Deref trait + type Target = str; + fn deref(&self) -> &str { + self.0.as_str() + } +} + +fn print(s: &str) { + println!("s: {:?}", s); +} +fn main() { + let y = MyString("Hello".to_string()); + print(&y); //此处发生隐式转换,&y从&MyString自动转换为&str类型,转换由&触发,过程如下: + // &y隐式转换过程: &MyString -> &str +} +``` + +上面代码的关键点为: + +- MyString实现了Deref特征,可以在需要时自动被转换为&str类型; +- &y是一个&MyString类型,当它被传给print函数时,自动通过Deref转换成了&str; +- 必须使用&y的方式来触发Deref(仅引用类型的实参才会触发自动解引用)。 + +下面为调用方法时发生的隐式自动转换的例子: +```Rust +use std::ops::Deref; +struct MyType(u32); +impl MyType { + fn to_u32(&self) -> u32 { + self.0 + } +} + +struct MyBox(MyType); +impl Deref for MyBox { + // MyString类型实现了Deref trait + type Target = MyType; + fn deref(&self) -> &MyType { + &self.0 + } +} + +fn main() { + let mb = MyBox(MyType(5u32)); + let _a = mb.to_u32(); // 此行发生隐式转换,转换过程分析如下: + // let _a = mb.to_u32() 等价于 let _a = MyType::to_u32(&mb) + // MyType::to_u32(&mb)中, + // 1、由&触发隐式转换, &mb从&MyBox转换到&MyType + // 2、调用MyType的to_u32方法 +} +``` + +Deref还支持连续的隐式转换,示例如下: +```Rust +fn print(s: &str) { + println!("{}", s); +} +fn main() { + let s = Box::new(String::from("hello world")); + print(&s) // &s隐式转换过程:&Box -> &String -> &str +} +``` +上面的代码中,Box、String都实现了Deref,当把&s传入到print函数时,发送连续隐式转换(&s先从&Box转换到&String,再转换到&str)。 + +## 5. Deref强制转换与可变性交互 +类似于如何使用Dereftrait 重载不可变引用的*运算符,Rust提供了DerefMut trait用于重载可变引用的*运算符。 + +Rust在发现类型和 trait 实现满足三种情况时会进行 Deref 强制转换: + +- 当T: Deref时从&T到&U。 +- 当T: DerefMut时从&mut T到&mut U。 +- 当 T: Deref时从&mut T到&U。