点运算符
点运算符会执行很多类型转换的魔法。它会执行自动引用、自动解引用和强制转换,直到类型匹配。方法查找的详细机制在这里定义,但这里有一个简要概述,概述了主要步骤。
假设我们有一个函数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_cloned
和 bar_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>
。