点运算符

点运算符将执行很多类型转换的魔法:它将执行自动引用、自动去引用和强制转换,直到类型匹配。方法查找的详细机制定义在这里,简要的概述如下:

假设我们有一个函数foo,它有一个接收器(一个self&self&mut self参数)。如果我们调用value.foo(),编译器需要确定Self是什么类型,然后才能调用该函数的正确实现。在这个例子中,我们将说value具有T类型。

我们将使用full-qualified syntax来更清楚地说明我们到底是在哪个类型上调用一个函数。

  • 首先,编译器会检查是否可以直接调用T::foo(value)。这被称为“按值”方法调用。
  • 如果它不能调用这个函数(例如,如果这个函数的类型不对,或者一个 trait 没有为Self实现),那么编译器就会尝试添加一个自动引用。这意味着编译器会尝试<&T>::foo(value)<&mut T>::foo(value)。这被称为“autoref”方法调用。
  • 如果这些候选方法都不奏效,它就对T解引用并再次尝试。这使用了Deref特性——如果T: Deref<Target = U>,那么它就用U而不是T类型再试。如果它不能解除对T的引用,它也可以尝试 unsizingT。这只是意味着,如果T在编译时有一个已知的大小参数,那么在解析方法时它就会“忘记”它。例如,这个 unsizing 步骤可以通过“忘记”数组的大小将[i32; 2]转换成[i32]

下面是一个方法查找算法的例子:

let array: Rc<Box<[T; 3]>> = ...;
let first_entry = array[0];

当数组在这么多的间接点后面时,编译器是如何实际计算array[0]的呢?首先,array[0]实际上只是Index特性的语法糖——编译器会将array[0]转换成array.index(0)。现在,编译器检查array是否实现了Index,这样它就可以调用这个函数。

然后,编译器检查Rc<Box<[T; 3]>>是否实现了Index,但它没有,&Rc<Box<[T; 3]>>&mut Rc<Box<[T; 3]>>也没有。由于这些方法都不起作用,编译器将Rc<Box<[T; 3]>解引用到Box<[T; 3]>中,并再次尝试。Box<[T; 3]>&Box<[T; 3]>&mut Box<[T; 3]>没有实现Index,所以它再次解引用。[T; 3]和它的自动引用也没有实现Index。它不能再继续解引用[T; 3],所以编译器取消了它的大小,得到了[T]。最后,[T]实现了Index,所以它现在可以调用实际的index函数。

考虑一下下面这个更复杂的点运算符工作的例子:


#![allow(unused)]
fn main() {
fn do_stuff<T: Clone>(value: &T) {
    let cloned = value.clone();
}
}

实现了Clone的是什么类型?首先,编译器检查是否可以按值调用。value的类型是&T,所以clone函数的签名是fn clone(&T) -> T。它知道T: Clone,所以编译器发现cloned: T

如果取消T: Clone的限制,会发生什么?它将不能按值调用,因为T没有实现Clone。所以编译器会尝试通过自动搜索来调用。在这种情况下,该函数的签名是fn clone(&T) -> &T,因为Self = &T。编译器看到&T: Clone,然后推断出cloned: &T

下面是另一个例子,自动搜索行为被用来创造一些微妙的效果:


#![allow(unused)]
fn main() {
use std::sync::Arc;

#[derive(Clone)]
struct Container<T>(Arc<T>);

fn clone_containers<T>(foo: &Container<i32>, bar: &Container<T>) {
    let foo_cloned = foo.clone();
    let bar_cloned = bar.clone();
}
}

foo_clonedbar_cloned是什么类型?我们知道,Container<i32>: Clone,所以编译器按值调用clone,得到foo_cloned: Container<i32>。然而,bar_cloned实际上有&Container<T>类型。这肯定是不合理的——我们给Container添加了#[derive(Clone)],所以它必须实现Clone! 仔细看看,由derive宏产生的代码是(大致):

impl<T> Clone for Container<T> where T: Clone {
    fn clone(&self) -> Self {
        Self(Arc::clone(&self.0))
    }
}

派生的Clone实现是只在T: Clone的地方定义,所以没有Container<T>的实现。Clone在一般的T上没有实现。编译器接着查看&Container<T>是否实现了Clone,最终发现它实现了。因此,它推断出clone是由 autoref 调用的,所以bar_cloned的类型是&Container<T>

我们可以通过手动实现Clone而不需要T: Clone来解决这个问题:

impl<T> Clone for Container<T> {
    fn clone(&self) -> Self {
        Self(Arc::clone(&self.0))
    }
}

现在,类型检查器推断出,bar_cloned: Container<T>