Post

🧀 Deref和Deref Coercion

&String==&str???

1
2
3
4
5
6
7
8
9
10
11
fn test(v: &str)->{
	println!("{}",v);
}


fn main(){
	let a = String::from("hello");
	assert_eq!("hello",a);
	test(&a);
}

因为在Rust中,我们知道 hello 是 str 类型

那这里 assert_eq!("hello",a) 会啥可以对比呢?

test方法需要 &str 但是我们传入 &String 为啥又可以?

先说结论,这里涉及到

  1. deref
  2. deref coercion

Deref trait

derefDeref trait 中唯一的方法

实现这个trait有什么用呢???**

Implementing the Deref trait allows you to customize the behavior of the dereference operator *

也就是,比如 let b=&a, 在操作 *b 的时候,其实会调用定义的deref() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct MyBox<T>(T);

impl<T> MyBox<T>{
    fn new(v: T) -> Self{
        MyBox(v)
    }
}
use std::ops::Deref;

impl<T> Deref for MyBox<T>{
    type Target = T;

    fn deref(&self) -> &Self::Target{
        &self.0
    }
}

fn main(){

    let a = 5;
    let b = MyBox::new(x);

    assert_eq!(5, a);
    assert_eq!(5, *b);

}

哪里调用了?

对于任何实现了 Deref 特征的类型,compiler在看到 *b 的时候,它背后其实是在做 *(b.deref())

b.deref() 返回的是一个引用,&Self::Target

那么*(b.deref())中的*就是正常的解引用

deref方法之所以要返回引用,是因为不让所有权转移

assert_eq!(“hello”,a)

assert_eq! 是一个宏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
macro_rules! assert_eq {
    ($left:expr, $right:expr $(,)?) => {
        match (&$left, &$right) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    let kind = $crate::panicking::AssertKind::Eq;
                    // The reborrows below are intentional. Without them, the stack slot for the
                    // borrow is initialized even before the values are compared, leading to a
                    // noticeable slow down.
                    $crate::panicking::assert_failed(kind, &*left_val, &*right_val, $crate::option::Option::None);
                }
            }
        }
    };
	//...
}

match 里面的通过模式匹配 解构出来的 left_valright_val 两个是 reference ,

因为match 的是 (&xxx,&xxx)

1
2
	let a = String::from("hello");
	assert_eq!("hello",a);

代入assert_eq!("hello",a) ,left_val 就是 &str, right_val就是 &String

这两个的deref都是定义在std里面

if !(*left_val == *right_val) { 这一步就是上面讨论的了

那这里一个是 &str 一个是 &String

因为 String 类型实现的 Deref

fn deref(&self) -> &str 它返回的是&str

Deref Coercion

Coercion: the use of force to persuade someone to do something that they are unwilling to do

这个特性也是Rust编译器会帮助我们实现的,它是在干什么呢?

Deref coercion converts a reference to a type that implements the Deref trait into a reference to another type.

就好像上面的 String 实现了 Deref trait,它返回 &str

所以

1
2
3
4
5
6
7
8
9
fn test(v: &str)->{
	println!("{}",v);
}

fn main(){
	let a = String::from("hello");
	test(&a);
}

这里当 一个函数的参数需要 &str 但我传进来 &String的时候,编译器会进行自动转换 注意这里参数类型是 reference

不然的话,我们需要手动 * 操作,这里也会涉及到deref*(x.deref()),然后得到值,然后再 & 得到值的引用

编译器就帮我们做了这一步了,

A sequence of calls to the deref method converts the type we provided into the type the parameter needs.

call deref 这个过程是可以一直下去的,如果需要的话,直到可以找到参数需要的类型

重要Demo!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct MyBox<T>(T);

impl<T> MyBox<T>{
    fn new(v: T) -> Self{
        MyBox(v)
    }
}
use std::ops::Deref;

impl<T> Deref for MyBox<T>{
    type Target = T;

    fn deref(&self) -> &Self::Target{
        &self.0
    }
}

fn hello(name: &str){
	println!("hello,{name}");
}

fn main(){
	let m = MyBox::new(String::from("Rust"));
	hello(&m);
}

这里hello 参数需要的是 &str

但是我们传入了&MyBox<String> 类型

不过没关系,MyBox<T> 实现了 Deref

所以这里编译器可以帮我们从 &MyBox<String> 得到 &String,

&String与目标&str 也不匹配,

不过没关系,String 实现了Deref (std实现的)

所以这里编译器可以帮我们从&String 得到 &str

从而编译通过

DerefMut

Deref trait 是在 对不可变引用 进行* 操作, 如果要对 可变引用进行 * 操作,那么需要实现 DerefMut trait

那么现在deref coercion可以处理以下三种情况:

  • From &T to &U when T: Deref<Target=U>
  • From &mut T to &mut U when T: DerefMut<Target=U>
  • From &mut T to &U when T: Deref<Target=U>

前两个都很好理解 比较重要的是第三个,但其实这里也就是说明了一点,可以从可变转到不可变,也就是参数申明要的是 &U,但我传进来 &mut T

但不可以从不可变转到可变。这也很好理解,因为Rust需要保证任何时候最多只有一个可变引用。

This post is licensed under CC BY 4.0 by the author.