这篇文章将解答如下几个疑问
- 为什么在参数类型&str的地方可以传递&String ?
- 为什么结构体或元组的引用也可以访问其字段(属性)?
T.a ===> (&T).a ✔- 为什么实例对象可以直接调用需要传递引用的方法?
fn f(&self){} ===> T.f() ✔
为什么println!("{}", T / &T) 传递引用或非引用都可以?
1 若传参类型实现了Deref trait,则可以隐式地将其引用类型转化为另一种引用类型
【参考文档】Dref相关
Deref coercion converts a reference to a type that implements theDereftrait into a reference to another type.
p.s 上文官翻有点问题,应该是 A的引用到B的引用
- Deref trait 可以重载解
*解引用运算符
// Dref签名
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
🌰
use std::ops::Deref;
struct A;
impl Deref for A {
type Target = i32;
fn deref(&self) -> &Self::Target { &1 }
}
fn main () {
println!("{}", *A)
}
// 打印结果:
// 1
- &A where A: Deref ===> &B
// 接上面的栗子,省略A的相关实现...
fn test(n: &i32) {println!("{}", n)}
fn main () {
test(&A); //因为A实现了Deref,所以在传递&A时,当编译器发现与函数所需类型&i32不符时,会自动调用其deref方法,即test(&1)
}
// 打印结果:
// 1
【解答1】因为String实现了Deref, 其返回的是&str, 所以在需要&str作为参数的地方,可以传&String
2 若容器实现了Deref或DerefMut,编译器会尝试多次解引用,以使字段访问正常
【参考文档】
- 字段访问时的解引用
- 引用的实现
所有的引用类型数据都完成了Deref实现
// &T Deref实现
impl<T: ?Sized> Deref for &T {
type Target = T;
#[rustc_diagnostic_item = "noop_method_deref"]
fn deref(&self) -> &T {
*self
}
}
// &mut T Deref实现
impl<T: ?Sized> Deref for &mut T {
type Target = T;
fn deref(&self) -> &T {
*self
}
}
(p.s 尽管*操作符本来就是可以对&修饰的数据进行解引用, 这里实现的意义更多可以看成一种类型补全,比如它让程序在需要传递T: Deref的地方也具备了传递引用类型数据的能力)
【解答2】&T实现了Deref,根据规则会尝试解引用,此时有(*(&T)).a,即T.a
struct B(i32);
fn main () {
let b = B(1);
println!("{}", (&b).0) //注意:由于运算符存在优先权,(&b).0得到的是i32, 而&b.0是&i32
// 输出结果:1
}
结合前面的内容,再看个稍微再复杂的🌰
use std::ops::Deref;
struct B(i32);
struct A;
impl Deref for A {
type Target = B;
fn deref(&self) -> &Self::Target {
&B(1)
}
}
fn main () {
println!("{}", (&A).0)
// 打印结果:1
// 解引用过程:第一次 (*&(A)).0 -> A.0, 第二次 (*(A.deref())).0 -> B(1).0
}
3 实例对象在调用方法时,会尝试多次解引用或借用,以调用该方法
参考文档: 方法调用时的解引用和借用
When looking up a method call, the receiver may be automatically dereferenced or borrowed in order to call a method.
【解答3-1】T.f()会尝试使用借用的方式,即&T,以满足方法fn f(&self){}
struct A;
impl A {
fn test (&self) {}
}
fn main () {
A.test() // 因为不满足方法所需的&Self, 这里尝试了借用&A
}
结合前面的内容,再看个稍微再复杂的🌰
use std::ops::Deref;
struct A;
impl A {
fn test (&self) {}
}
struct B;
impl Deref for B {
type Target = A;
fn deref(&self) -> &Self::Target {
&A
}
}
fn main () {
B.test()
// 接收者(实例对象)类型为B,
// 候选类型为B, &B, &mut B, A(通过*B解引用得到), &A, &mut A
// 这里&A符合调用需求
}
【解答3-2 println!宏也同样是种语法糖,需要对象实现fmt方法才能正常打印,而在对象调用fmt方法的过程中,都会做出解引用或借用的尝试,所以传递引用或非引用都是有效的】