方法调用表达式
一个方法调用由一个表达式(即接收者)后跟一个点、一个表达式路径片段以及一个带括号的表达式列表构成。
方法调用会被解析为特定 trait 上的关联方法,如果左侧表达式的准确 self 类型已知,则静态分派到该方法;如果左侧表达式是间接的trait object,则动态分派。
#![allow(unused)] fn main() { let pi: Result<f32, _> = "3.14".parse(); let log_pi = pi.unwrap_or(1.0).log(2.72); assert!(1.14 < log_pi && log_pi < 1.15) }
在查找方法调用时,接收者可能会被自动解引用或借用以便调用方法。这比查找其他函数需要更复杂的查找过程,因为可能有多种可能的方法可以调用。使用以下过程进行查找:
第一步是构建一个候选接收者类型列表。通过重复解引用接收者表达式的类型来获得这些类型,将遇到的每个类型添加到列表中,最后尝试进行非 Sized 强制转换,如果成功则添加结果类型。
然后,对于每个候选类型 T,立即在 T 之后将 &T 和 &mut T 添加到列表中。
例如,如果接收者的类型是 Box<[i32;2]>,则候选类型将是 Box<[i32;2]>、&Box<[i32;2]>、&mut Box<[i32;2]>、[i32; 2](通过解引用)、&[i32; 2]、&mut [i32; 2]、[i32](通过非 Sized 强制转换)、&[i32],最后是 &mut [i32]。
然后,对于每个候选类型 T,在以下位置搜索具有该类型接收者的可见方法:
T的固有方法(直接在T上实现的方法)。- 由
T实现的任何可见 trait 提供的方法。如果T是一个类型参数,则首先查找由T上的 trait bound 提供的方法。然后查找作用域中的所有剩余方法。
注意
查找是按顺序对每种类型进行的,这有时可能导致令人惊讶的结果。以下代码将打印“In trait impl!”,因为首先查找
&self方法,在找到 struct 的&mut self方法之前就找到了 trait 方法。struct Foo {} trait Bar { fn bar(&self); } impl Foo { fn bar(&mut self) { println!("In struct impl!") } } impl Bar for Foo { fn bar(&self) { println!("In trait impl!") } } fn main() { let mut f = Foo{}; f.bar(); }
如果这导致多个可能的候选者,则会产生错误,并且接收者必须被转换为适当的接收者类型才能进行方法调用。
此过程不考虑接收者的可变性或生命周期,也不考虑方法是否为 unsafe。方法查找完成后,如果由于一个(或多个)这些原因无法调用,则结果将是编译器错误。
如果在某一步骤中存在多个可能的方法(例如,泛型方法或 trait 被视为相同),则会产生编译器错误。这些情况需要使用消歧义函数调用语法来调用方法和函数。
版本差异:在 2021 版本之前,在搜索可见方法时,如果候选接收者类型是数组类型,则会忽略标准库
IntoIteratortrait 提供的方法。为此目的使用的版本由表示方法名称的 token 决定。
未来可能会移除这种特殊情况。
警告
对于trait object,如果存在与 trait 方法同名的固有方法,则在方法调用表达式中尝试调用该方法时会产生编译器错误。相反,可以使用消歧义函数调用语法来调用该方法,在这种情况下它调用的是 trait 方法,而不是固有方法。无法调用固有方法。只需不要在 trait object 上定义与 trait 方法同名的固有方法即可解决问题。