点运算符

点运算符会执行很多类型转换的魔法。它会执行自动引用、自动解引用和强制转换,直到类型匹配。方法查找的详细机制在这里定义,但这里有一个简要概述,概述了主要步骤。

假设我们有一个函数foo,它有一个接收器(一个self&self&mut self参数)。如果我们调用value.foo(),编译器需要确定类型Self是什么,才能调用函数的正确实现。对于此示例,我们将说value的类型为T

我们将使用完全限定的语法,以更清楚地了解我们正在哪个类型上调用函数。

  • 首先,编译器检查是否可以直接调用T::foo(value)。这被称为“按值”方法调用。
  • 如果它不能调用此函数(例如,如果函数具有错误的类型或 trait 没有为 Self 实现),则编译器会尝试添加一个自动引用。这意味着编译器会尝试 <&T>::foo(value)<&mut T>::foo(value)。这被称为“自动引用”方法调用。
  • 如果这些候选项都不起作用,它会解引用 T 并再次尝试。这使用 Deref trait - 如果 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 trait 的语法糖 - 编译器会将 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。 所以编译器尝试通过自动引用调用。 在这种情况下,由于 Self = &T,函数具有签名 fn clone(&&T) -> &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>