April 18, 2019

自动解引用.RUST

解引用应该说是 Rust 为了解决不采用 Class 来实现对象化编程的一个解决方案。假想一下如果 Python 或者 Java 之流,需要对一个结构体(准确来说应该是类)进行自定义扩展:增加字段,增加方法,重写方法等等,我们可以直接用继承的方式来实现

class Base:
	a: int = 2

class Extend(Base):
	my_self_field: int = 3

当一个函数希望传入实现了 Base 类的所有实例时,可以直接以 Base 为约束,限定其参数范围。在 Java 中就可以使用基类或者 Interface 来约束。

def base_bound(param: Base):
    pass

这一套在 Rust 并不适用,在 Rust 中时采用 Struct + Trait 来抽象对象化。所以若想对结构体进行扩展,那么就只能再用一层结构体去包(wrap)住原来的结构体。

struct MyOwnDerefStruct(String);

这里我们就对 String 进行了自己的封装,这里并没有对字段进行扩展,但是这确实在 Rust 中比较常见的场景:一旦我们希望对某个特定的 Struct 实现某个特定的 Trait,同时 Struct 和 Trait 都来自第三方库(不在当前库中定义),那么为了实现impl Trait for Struct ,我们就需要解决孤儿定律(Orphan Rule),此时我们就可以用这种简单的包装方式来满足他。

什么是孤儿定律 Orphan Rule?

在 Rust 中, 若想对 Struct 实现一个 Trait, 那么 Struct 和 Trait 一定要有一方是在当前库中定义的。

这个约束很好理解,也很适用。

假设一个场景:C 库中对 A 的 Struct 实现了 B 中的 Trait。此时我们在当前库中使用了 C 库和 A 库,那么我们可能会对 A 中的 Struct 误解,其可能已经被继承了很多奇怪的 Trait,会严重影响我们对 Struct 的使用

同时,Rust 为了移除运行时和 GC 的消耗,实现了诸多智能指针:Box Rc Arc 等等。所以可能会出现诸如以下的包装 let param: Arc<Mutex<Box<Vec<i32>>>>; 这种在 Python 中只是简单的 a: List<int> 的包装。

为了方便这种因为语言特性导致的额外包装,Rust 提供了自动解引用 Deref 来简化编程。

#[lang = "deref"]
#[doc(alias = "*")]
#[doc(alias = "&*")]
#[stable(feature = "rust1", since = "1.0.0")]
pub trait Deref {
    /// The resulting type after dereferencing.
    #[stable(feature = "rust1", since = "1.0.0")]
    type Target: ?Sized;

    /// Dereferences the value.
    #[must_use]
    #[stable(feature = "rust1", since = "1.0.0")]
    fn deref(&self) -> &Self::Target;
}

若要使用 Deref Trait,我们先看看 Deref 里面有什么东西:

  • type Target 指的是我们希望被解引用到那个数据结构。
  • deref() 提供了一个手动调用解引用到 &Target 的方法。

回到我们写的 MyOwnDerefStruct 例子,我们包装了 String 类型,如果现在有一个接收 &String 参数的函数,在没有 Deref 的场景我们需要怎么调用他呢?

struct MyOwnDerefStruct(String);

fn print(s: &String) {
    println!("{}", s);
}

fn main() {
    let deref_struct = MyOwnDerefStruct(String::from("hello world"));
    print(&deref_struct.0);
}

在 L9 中,我们需要先通过 deref_struct.0 获取到 MyOwnDerefStruct 中的第一个属性 String ,然后再通过 & 来转换成 &String

如果我们直接用会 C 中的逻辑 print(&deref_struct) ,我们会得到以下的错误信息:

error[E0308]: mismatched types
  --> src/main.rs:23:11
   |
23 |     print(&deref_struct);
   |           ^^^^^^^^^^^^^ expected struct `std::string::String`, found struct `MyOwnDerefStruct`
   |
   = note: expected type `&std::string::String`
              found type `&MyOwnDerefStruct`

此时如果我们为我们的结构体 MyOwnDerefStruct 实现自动解引用的话,以上代码就可以正常编译:

impl Deref for MyOwnDerefStruct {
    type Target = String;

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

在上述代码中,我们告诉 Rust 编译器:「我们期望 MyOwnDerefStruct 被解到 String」,那么在编译过程中碰到需要 &String 时,编译器会自动帮我们转换。而且我们还能写出以下相当 tricky 的代码 print(&&&&&&&&&&&&&&&deref_struct);(这代码能跑的原因,可以自行去看看 Rust 内部对 & 的处理)

自动解引用还有一个好处就是,他可以直接寻址到 Target 的方法。在我们的场景里面,String 中有判断两个字符串是否相等的方法 eq 。因为自动解引用的存在,我们并不需要 deref_struct.0.eq("hello world") 的写法。 MyOwnDerefStruct 可以直接调用 eq 方法。

assert_eq!(true, deref_struct.eq("hello world"));

这样就避免了在使用智能指针的时候在代码中出现大量的 variable_box.0.method() 。避免了 .0 的出现,大大地简化了代码,同时也增加了代码的可读性。

variable.deref()*variable 的区别

使用解引用的时候需要注意的是函数 deref()* 的行为是不一样的。

  • deref() 的函数原型是 fn deref(&self) -> &Self::Target; 所以我们拿到的是 Target 的引用 &Target
  • * 是直接拿到 Target

所以,简单来说 variable.deref() 就等价于 &*variable