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