您现在的位置是:首页 >学无止境 >RUST 每日一省:解引用Deref网站首页学无止境

RUST 每日一省:解引用Deref

许汶强 2024-06-14 17:19:17
简介RUST 每日一省:解引用Deref

        “解引用”(Deref) 是“取引用”(Ref) 的反操作。 取引用, 我们有&、 &mut等操作符, 对应的, 解引用, 我们有*操作符。 默认的“取引用”、 “解引用”操作是互补抵消的关系, 互为逆运算。

fn main() {
    let v1 = 1;
    let p = &v1; //引用
    let v2 = *p; //解引用
    println!("{} {}", v1, v2);
} 

 上例中, v1的类型是i32, p的类型是&i32, *p的类型又返回i32。 

自定义解引用   

        自动解引用虽然是编译器来做的, 但是自动解引用的行为可以由开发者来定义。  
        通过实现 std::ops::Deref 和 std::ops::DerefMut  trait,可以修改解引用操作符 * 和 . 在自定义类型上的行为。
Deref的定义如下所示。 DerefMut的唯一区别是返回的是&mut型引用。

trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}
trait DerefMut: Deref {
    fn deref_mut(&mut self) -> &mut Self::Target;
} 

比如, 标准库中实现了Box的解引用: 

impl<T: ?Sized, A: Allocator> const Deref for Box<T, A> {
    type Target = T;
fn deref(&self) -> &T {
        &**self
    }
}

我们可以使用*y提取出来Box里的值。

fn main() {
    let x = 6;
    let y = Box::new(x);
    assert_eq!(6, x);
    assert_eq!(6, *y);
    //assert_eq!(6, y);  error    
}

        请大家注意这里的类型, deref() 方法返回的类型是&Target, 而不是Target。 如果说有变量y的类型为Box, *y的类型并不等于y.deref() 的类型。 *y的类型实际上是Target, 即i32。 


自动解引用 

        Rust提供的“自动解引用”机制, 是在某些场景下“隐式地”“自动地”帮我们做了一些事情。 什么是自动解引用呢? 

自动deref的规则是,

  • 当T: Deref<Target=U>时,允许&T转换为&U。 
  • 当T: DerefMut<Target=U>时,允许&mut T转换为&mut U。
  • 当T: Deref<Target=U>时,允许&mut T转换为&U。 
fn main() {
    let x = "hello world";
    let y = Box::new(String::from("hello world"));
    assert_eq!("hello world", x);
    assert_eq!("hello world", *y);
}

        Box实现了Deref trait,所以Rust可以通过调用deref来将Box<String>转换为&String。因为标准库为String提供的Deref实现会返回字符串切片,所以Rust可以继续调用deref来将&String转换为&str,并最终与“hello world”相匹配。 

 
冲突手动处理

        如果智能指针中的方法与它内部成员的方法冲突了怎么办呢? 编译器会优先调用当前最匹配的类型, 而不会执行自动deref, 在这种情况下, 我们就只能手动deref来表达我们的需求了。
clone方法在Rc和&str类型中都被实现了, 所以调用时会直接调用Rc的clone方法, 如果想调用Rc里面&str类型的clone方法, 则需要使用“解引用”操作符手动解引用。 

fn main() {
    let a = Rc::new(String::from("hello world"));
    let b = a.clone();
    let c = (*a).clone();   
}

在match表达式中 ,引用也需要手动解引用 

fn main() {
    let s = String::new();
    match &s {
        "" => {}
        _ => {}
    }
}


match后面的变量类型是&String, 匹配分支的变量类型为&'static str,这种情况下就需要我们手动完成类型转换了。 手动将&String类型转换为&str类型的办法如下。

1) match s.deref()。这个方法通过主动调用deref()方法达到类型转换的目的。 此时我们需要引入Deref trait方可通过编译, 即加上代码use std::ops::Deref; 。

2) match &*s。 我们可以通过*s运算符, 也可以强制调用deref()方法, 与上面的做法一样。

3) match s.as_ref() 。 这个方法调用的是标准库中的std::convert::AsRef方法, 这个trait存在于prelude中, 无须手工引入即可使用。

4) match s.borrow()。 这个方法调用的是标准库中的std::borrow::Borrow方法。 要使用它, 需要加上代码use std::borrow::Borrow; 

5) match &s[..]。 这个方案也是可以的, 这里利用了String重载的Index操作。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。