点运算符

点运算符会执行许多神奇的操作来转换类型。它会执行自动引用、自动解引用和强制转换,直到类型匹配。方法查找的详细机制定义在此处,但这里简要概述了主要步骤。

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

我们将使用完全限定语法来更清楚地说明我们到底在调用哪个类型的函数。

  • 首先,编译器会检查是否可以直接调用 T::foo(value)。这称为“按值”方法调用。
  • 如果它不能调用此函数(例如,如果函数的类型错误或未为 Self 实现特征),则编译器会尝试添加自动引用。这意味着编译器会尝试 <&T>::foo(value)<&mut T>::foo(value)。这称为“自动引用”方法调用。
  • 如果这些候选方法都不起作用,它会解引用 T 并重试。这使用 Deref 特征 - 如果 T: Deref<Target = U>,则它会尝试使用类型 U 而不是 T。如果它不能解引用 T,它也可以尝试对 T 进行“取消大小”。这意味着如果 T 有一个在编译时已知的大小参数,它会在解析方法时“忘记”它。例如,此取消大小步骤可以通过“忘记”数组的大小将 [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();
}
}

cloned 的类型是什么?首先,编译器会检查是否可以按值调用。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 时定义,因此对于泛型 T,没有 Container<T>: Clone 的实现。然后,编译器会查看 &Container<T> 是否实现了 Clone,它确实实现了。因此它推断出 clone 是通过自动引用调用的,因此 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>