Rust 的自动解引用规则是什么?
What are Rust's exact auto-dereferencing rules?
我 learning/experimenting 喜欢 Rust,在我发现的这门语言的所有优雅中,有一个特点让我感到困惑,而且似乎完全不合时宜。
Rust 在进行方法调用时自动取消引用指针。我做了一些测试以确定确切的行为:
struct X { val: i32 }
impl std::ops::Deref for X {
type Target = i32;
fn deref(&self) -> &i32 { &self.val }
}
trait M { fn m(self); }
impl M for i32 { fn m(self) { println!("i32::m()"); } }
impl M for X { fn m(self) { println!("X::m()"); } }
impl M for &X { fn m(self) { println!("&X::m()"); } }
impl M for &&X { fn m(self) { println!("&&X::m()"); } }
impl M for &&&X { fn m(self) { println!("&&&X::m()"); } }
trait RefM { fn refm(&self); }
impl RefM for i32 { fn refm(&self) { println!("i32::refm()"); } }
impl RefM for X { fn refm(&self) { println!("X::refm()"); } }
impl RefM for &X { fn refm(&self) { println!("&X::refm()"); } }
impl RefM for &&X { fn refm(&self) { println!("&&X::refm()"); } }
impl RefM for &&&X { fn refm(&self) { println!("&&&X::refm()"); } }
struct Y { val: i32 }
impl std::ops::Deref for Y {
type Target = i32;
fn deref(&self) -> &i32 { &self.val }
}
struct Z { val: Y }
impl std::ops::Deref for Z {
type Target = Y;
fn deref(&self) -> &Y { &self.val }
}
#[derive(Clone, Copy)]
struct A;
impl M for A { fn m(self) { println!("A::m()"); } }
impl M for &&&A { fn m(self) { println!("&&&A::m()"); } }
impl RefM for A { fn refm(&self) { println!("A::refm()"); } }
impl RefM for &&&A { fn refm(&self) { println!("&&&A::refm()"); } }
fn main() {
// I'll use @ to denote left side of the dot operator
(*X{val:42}).m(); // i32::m() , Self == @
X{val:42}.m(); // X::m() , Self == @
(&X{val:42}).m(); // &X::m() , Self == @
(&&X{val:42}).m(); // &&X::m() , Self == @
(&&&X{val:42}).m(); // &&&X:m() , Self == @
(&&&&X{val:42}).m(); // &&&X::m() , Self == *@
(&&&&&X{val:42}).m(); // &&&X::m() , Self == **@
println!("-------------------------");
(*X{val:42}).refm(); // i32::refm() , Self == @
X{val:42}.refm(); // X::refm() , Self == @
(&X{val:42}).refm(); // X::refm() , Self == *@
(&&X{val:42}).refm(); // &X::refm() , Self == *@
(&&&X{val:42}).refm(); // &&X::refm() , Self == *@
(&&&&X{val:42}).refm(); // &&&X::refm(), Self == *@
(&&&&&X{val:42}).refm(); // &&&X::refm(), Self == **@
println!("-------------------------");
Y{val:42}.refm(); // i32::refm() , Self == *@
Z{val:Y{val:42}}.refm(); // i32::refm() , Self == **@
println!("-------------------------");
A.m(); // A::m() , Self == @
// without the Copy trait, (&A).m() would be a compilation error:
// cannot move out of borrowed content
(&A).m(); // A::m() , Self == *@
(&&A).m(); // &&&A::m() , Self == &@
(&&&A).m(); // &&&A::m() , Self == @
A.refm(); // A::refm() , Self == @
(&A).refm(); // A::refm() , Self == *@
(&&A).refm(); // A::refm() , Self == **@
(&&&A).refm(); // &&&A::refm(), Self == @
}
所以,似乎,或多或少:
- 编译器将根据需要插入尽可能多的解引用运算符来调用方法。
- 编译器在解析使用
&self
声明的方法时(按引用调用):
- 首先尝试调用
self
的单个取消引用
- 然后尝试调用
self
的确切类型
- 然后,尝试为匹配项插入尽可能多的解引用运算符
- 使用
self
(按值调用)类型 T
声明的方法与使用 &self
(按引用调用)类型 [=] 声明的方法一样17=] 并调用对点运算符左侧任何内容的引用。
- 首先使用原始内置解引用尝试上述规则,如果没有匹配,则使用具有
Deref
特征的重载。
确切的自动取消引用规则是什么?任何人都可以为这样的设计决定给出任何正式的理由吗?
您的伪代码非常正确。对于这个例子,假设我们有一个方法调用 foo.bar()
where foo: T
。我将使用 fully qualified syntax (FQS) 来明确调用方法的类型,例如A::bar(foo)
或 A::bar(&***foo)
。我只是要写一堆随机的大写字母,每个都是一些任意的 type/trait,除了 T
总是调用方法的原始变量 foo
的类型上。
算法的核心是:
- 对于每个"dereference step"
U
(即设置U = T
然后U = *T
, ...)
- 如果有一个方法
bar
接收者类型(方法中self
的类型)完全匹配U
,使用它(a "by value method")
- 否则,添加一个自动引用(接收者的
&
或 &mut
),并且,如果某个方法的接收者匹配 &U
,则使用它(an "autorefd method")
值得注意的是,一切都考虑了方法的“接收者类型”,不是特征的Self
类型,即impl ... for Foo { fn method(&self) {} }
考虑&Foo
匹配方法时,fn method2(&mut self)
匹配时会考虑&mut Foo
。
如果在内部步骤中有多个有效的特征方法是错误的(也就是说,在1.或2.中的每一个中只能有零个或一个有效的特征方法,但可以有一个有效的对于每个:首先采用 1 中的那个),并且固有方法优先于特征方法。如果我们到达循环末尾而没有找到任何匹配项,这也是一个错误。递归 Deref
实现也是错误的,这会使循环无限(它们将达到“递归限制”)。
这些规则在大多数情况下似乎都符合我的意思,尽管在某些边缘情况下能够编写明确的 FQS 形式非常有用,并且对于宏生成代码的合理错误消息也是如此。
只添加了一个自动引用,因为
- 如果没有界限,事情就会变得bad/slow,因为每种类型都可以有任意数量的引用
- 获取一个引用
&foo
保留与 foo
的强连接(它是 foo
本身的地址),但获取更多引用开始失去它:&&foo
是存储 &foo
. 的堆栈上某个临时变量的地址
例子
假设我们有一个调用 foo.refm()
,如果 foo
的类型为:
X
,然后我们从 U = X
开始,refm
的接收器类型为 &...
,所以第 1 步不匹配,采用自动引用给我们&X
,这确实匹配(与 Self = X
),所以调用是 RefM::refm(&foo)
&X
,以U = &X
开头,在第一步匹配&self
(与Self = X
),所以调用是RefM::refm(foo)
&&&&&X
,这与任何一步都不匹配(&&&&X
或 &&&&&X
未实现该特征),因此我们取消引用一次以获得 U = &&&&X
, 匹配 1 (with Self = &&&X
) 调用是 RefM::refm(*foo)
Z
,在任何一步都不匹配,所以它被取消引用一次,得到 Y
,它也不匹配,所以它再次被取消引用,得到 X
,它不匹配 1,但在自动引用后匹配,所以调用是 RefM::refm(&**foo)
.
&&A
,1. 不匹配,2. 也不匹配,因为 &A
(对于 1)或 &&A
(对于 2)没有实现特征,所以它被取消引用到 &A
,它匹配 1.,与 Self = A
假设我们有 foo.m()
,而 A
不是 Copy
,如果 foo
有类型:
A
,然后 U = A
直接匹配 self
所以调用是 M::m(foo)
和 Self = A
&A
,那么 1. 不匹配,2. 也不匹配(&A
和 &&A
都没有实现该特征),因此它被取消引用为 A
,确实匹配,但是 M::m(*foo)
需要按值取 A
,因此移出 foo
,因此出现错误。
&&A
, 1. 不匹配,但 autorefing 给出 &&&A
,确实匹配,所以调用是 M::m(&foo)
和 Self = &&&A
.
(此答案基于the code, and is reasonably close to the (slightly outdated) README。compiler/language这部分的主要作者Niko Matsakis也浏览了此答案。)
Rust 参考有 a chapter about the method call expression。我复制了下面最重要的部分。提醒:我们说的是表达式recv.m()
,其中recv
在下面称为"receiver expression"。
The first step is to build a list of candidate receiver types. Obtain these by repeatedly dereferencing the receiver expression's type, adding each type encountered to the list, then finally attempting an unsized coercion at the end, and adding the result type if that is successful. Then, for each candidate T
, add &T
and &mut T
to the list immediately after T
.
For instance, if the receiver has type Box<[i32;2]>
, then the candidate types will be Box<[i32;2]>
, &Box<[i32;2]>
, &mut Box<[i32;2]>
, [i32; 2]
(by dereferencing), &[i32; 2]
, &mut [i32; 2]
, [i32]
(by unsized coercion), &[i32]
, and finally &mut [i32]
.
Then, for each candidate type T
, search for a visible method with a receiver of that type in the following places:
T
's inherent methods (methods implemented directly on T
[¹]).
- Any of the methods provided by a visible trait implemented by
T
. [...]
(关于 [¹] 的注意事项:我实际上认为这种措辞是错误的。I've opened an issue。让我们忽略括号中的那句话。)
让我们详细看一下您的代码中的几个示例!对于您的示例,我们可以忽略有关 "unsized coercion" 和 "inherent methods".
的部分
(*X{val:42}).m()
:接收表达式的类型是i32
。我们执行这些步骤:
- 正在创建候选接收器类型列表:
i32
不能取消引用,所以我们已经完成了第 1 步。列表:[i32]
- 接下来,我们添加
&i32
和 &mut i32
。列表:[i32, &i32, &mut i32]
- 正在为每个候选接收器类型搜索方法:
- 我们发现
<i32 as M>::m
具有接收器类型 i32
。所以我们已经完成了。
到目前为止很容易。现在我们挑一个比较难的例子:(&&A).m()
。接收者表达式的类型是 &&A
。我们执行这些步骤:
- 正在创建候选接收器类型列表:
&&A
可以取消引用 &A
,因此我们将其添加到列表中。 &A
可以再次取消引用,所以我们也将 A
添加到列表中。 A
无法取消引用,所以我们停止。列表:[&&A, &A, A]
- 接下来,对于列表中的每个类型
T
,我们在 T
之后立即添加 &T
和 &mut T
。列表:[&&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
- 正在为每个候选接收器类型搜索方法:
- 没有接收器类型为
&&A
的方法,因此我们转到列表中的下一个类型。
- 我们发现方法
<&&&A as M>::m
确实有接收者类型&&&A
。这样我们就完成了。
以下是所有示例的候选接收者列表。 ⟪x⟫
中的类型是 "won" 中的类型,即第一个可以找到拟合方法的类型。还请记住,列表中的第一个类型始终是接收者表达式的类型。最后,我将列表格式化为三行,但这只是格式化:这个列表是一个平面列表。
(*X{val:42}).m()
→<i32 as M>::m
[⟪i32⟫, &i32, &mut i32]
X{val:42}.m()
→<X as M>::m
[⟪X⟫, &X, &mut X,
i32, &i32, &mut i32]
(&X{val:42}).m()
→<&X as M>::m
[⟪&X⟫, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
(&&X{val:42}).m()
→<&&X as M>::m
[⟪&&X⟫, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
(&&&X{val:42}).m()
→<&&&X as M>::m
[⟪&&&X⟫, &&&&X, &mut &&&X,
&&X, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
(&&&&X{val:42}).m()
→<&&&X as M>::m
[&&&&X, &&&&&X, &mut &&&&X,
⟪&&&X⟫, &&&&X, &mut &&&X,
&&X, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
(&&&&&X{val:42}).m()
→<&&&X as M>::m
[&&&&&X, &&&&&&X, &mut &&&&&X,
&&&&X, &&&&&X, &mut &&&&X,
⟪&&&X⟫, &&&&X, &mut &&&X,
&&X, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
(*X{val:42}).refm()
→<i32 as RefM>::refm
[i32, ⟪&i32⟫, &mut i32]
X{val:42}.refm()
→<X as RefM>::refm
[X, ⟪&X⟫, &mut X,
i32, &i32, &mut i32]
(&X{val:42}).refm()
→<X as RefM>::refm
[⟪&X⟫, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
(&&X{val:42}).refm()
→<&X as RefM>::refm
[⟪&&X⟫, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
(&&&X{val:42}).refm()
→<&&X as RefM>::refm
[⟪&&&X⟫, &&&&X, &mut &&&X,
&&X, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
(&&&&X{val:42}).refm()
→<&&&X as RefM>::refm
[⟪&&&&X⟫, &&&&&X, &mut &&&&X,
&&&X, &&&&X, &mut &&&X,
&&X, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
(&&&&&X{val:42}).refm()
→<&&&X as RefM>::refm
[&&&&&X, &&&&&&X, &mut &&&&&X,
⟪&&&&X⟫, &&&&&X, &mut &&&&X,
&&&X, &&&&X, &mut &&&X,
&&X, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
Y{val:42}.refm()
→<i32 as RefM>::refm
[Y, &Y, &mut Y,
i32, ⟪&i32⟫, &mut i32]
Z{val:Y{val:42}}.refm()
→<i32 as RefM>::refm
[Z, &Z, &mut Z,
Y, &Y, &mut Y,
i32, ⟪&i32⟫, &mut i32]
A.m()
→<A as M>::m
[⟪A⟫, &A, &mut A]
(&A).m()
→<A as M>::m
[&A, &&A, &mut &A,
⟪A⟫, &A, &mut A]
(&&A).m()
→<&&&A as M>::m
[&&A, ⟪&&&A⟫, &mut &&A,
&A, &&A, &mut &A,
A, &A, &mut A]
(&&&A).m()
→<&&&A as M>::m
[⟪&&&A⟫, &&&&A, &mut &&&A,
&&A, &&&A, &mut &&A,
&A, &&A, &mut &A,
A, &A, &mut A]
A.refm()
→<A as RefM>::refm
[A, ⟪&A⟫, &mut A]
(&A).refm()
→<A as RefM>::refm
[⟪&A⟫, &&A, &mut &A,
A, &A, &mut A]
(&&A).refm()
→<A as RefM>::refm
[&&A, &&&A, &mut &&A,
⟪&A⟫, &&A, &mut &A,
A, &A, &mut A]
(&&&A).refm()
→<&&&A as RefM>::refm
[&&&A, ⟪&&&&A⟫, &mut &&&A,
&&A, &&&A, &mut &&A,
&A, &&A, &mut &A,
A, &A, &mut A]
这个问题困扰了我很久,尤其是这部分:
(*X{val:42}).refm(); // i32::refm() , Self == @
X{val:42}.refm(); // X::refm() , Self == @
(&X{val:42}).refm(); // X::refm() , Self == *@
(&&X{val:42}).refm(); // &X::refm() , Self == *@
(&&&X{val:42}).refm(); // &&X::refm() , Self == *@
(&&&&X{val:42}).refm(); // &&&X::refm(), Self == *@
(&&&&&X{val:42}).refm(); // &&&X::refm(), Self == **@
直到我找到一种方法来记住这些奇怪的规则。我不确定这是否正确,但大多数时候这种方法是有效的。
关键是,在寻找使用哪个函数时,不要使用调用“点运算符”的类型来确定使用哪个“impl”,而是找到根据函数签名的函数,然后用函数签名.
判断“self”的类型
我将函数定义代码转换如下:
trait RefM { fn refm(&self); }
impl RefM for i32 { fn refm(&self) { println!("i32::refm()"); } }
// converted to: fn refm(&i32 ) { println!("i32::refm()"); }
// => type of 'self' : i32
// => type of parameter: &i32
impl RefM for X { fn refm(&self) { println!("X::refm()"); } }
// converted to: fn refm(&X ) { println!("X::refm()"); }
// => type of 'self' : X
// => type of parameter: &X
impl RefM for &X { fn refm(&self) { println!("&X::refm()"); } }
// converted to: fn refm(&&X ) { println!("&X::refm()"); }
// => type of 'self' : &X
// => type of parameter: &&X
impl RefM for &&X { fn refm(&self) { println!("&&X::refm()"); } }
// converted to: fn refm(&&&X ) { println!("&&X::refm()"); }
// => type of 'self' : &&X
// => type of parameter: &&&X
impl RefM for &&&X { fn refm(&self) { println!("&&&X::refm()"); } }
// converted to: fn refm(&&&&X) { println!("&&&X::refm()"); }
// => type of 'self' : &&&X
// => type of parameter: &&&&X
因此,当您编写代码时:
(&X{val:42}).refm();
函数
fn refm(&X ) { println!("X::refm()");
会被调用,因为参数类型是&X
.
如果没有找到匹配的函数签名,则执行自动引用或某些自动取消引用。
我 learning/experimenting 喜欢 Rust,在我发现的这门语言的所有优雅中,有一个特点让我感到困惑,而且似乎完全不合时宜。
Rust 在进行方法调用时自动取消引用指针。我做了一些测试以确定确切的行为:
struct X { val: i32 }
impl std::ops::Deref for X {
type Target = i32;
fn deref(&self) -> &i32 { &self.val }
}
trait M { fn m(self); }
impl M for i32 { fn m(self) { println!("i32::m()"); } }
impl M for X { fn m(self) { println!("X::m()"); } }
impl M for &X { fn m(self) { println!("&X::m()"); } }
impl M for &&X { fn m(self) { println!("&&X::m()"); } }
impl M for &&&X { fn m(self) { println!("&&&X::m()"); } }
trait RefM { fn refm(&self); }
impl RefM for i32 { fn refm(&self) { println!("i32::refm()"); } }
impl RefM for X { fn refm(&self) { println!("X::refm()"); } }
impl RefM for &X { fn refm(&self) { println!("&X::refm()"); } }
impl RefM for &&X { fn refm(&self) { println!("&&X::refm()"); } }
impl RefM for &&&X { fn refm(&self) { println!("&&&X::refm()"); } }
struct Y { val: i32 }
impl std::ops::Deref for Y {
type Target = i32;
fn deref(&self) -> &i32 { &self.val }
}
struct Z { val: Y }
impl std::ops::Deref for Z {
type Target = Y;
fn deref(&self) -> &Y { &self.val }
}
#[derive(Clone, Copy)]
struct A;
impl M for A { fn m(self) { println!("A::m()"); } }
impl M for &&&A { fn m(self) { println!("&&&A::m()"); } }
impl RefM for A { fn refm(&self) { println!("A::refm()"); } }
impl RefM for &&&A { fn refm(&self) { println!("&&&A::refm()"); } }
fn main() {
// I'll use @ to denote left side of the dot operator
(*X{val:42}).m(); // i32::m() , Self == @
X{val:42}.m(); // X::m() , Self == @
(&X{val:42}).m(); // &X::m() , Self == @
(&&X{val:42}).m(); // &&X::m() , Self == @
(&&&X{val:42}).m(); // &&&X:m() , Self == @
(&&&&X{val:42}).m(); // &&&X::m() , Self == *@
(&&&&&X{val:42}).m(); // &&&X::m() , Self == **@
println!("-------------------------");
(*X{val:42}).refm(); // i32::refm() , Self == @
X{val:42}.refm(); // X::refm() , Self == @
(&X{val:42}).refm(); // X::refm() , Self == *@
(&&X{val:42}).refm(); // &X::refm() , Self == *@
(&&&X{val:42}).refm(); // &&X::refm() , Self == *@
(&&&&X{val:42}).refm(); // &&&X::refm(), Self == *@
(&&&&&X{val:42}).refm(); // &&&X::refm(), Self == **@
println!("-------------------------");
Y{val:42}.refm(); // i32::refm() , Self == *@
Z{val:Y{val:42}}.refm(); // i32::refm() , Self == **@
println!("-------------------------");
A.m(); // A::m() , Self == @
// without the Copy trait, (&A).m() would be a compilation error:
// cannot move out of borrowed content
(&A).m(); // A::m() , Self == *@
(&&A).m(); // &&&A::m() , Self == &@
(&&&A).m(); // &&&A::m() , Self == @
A.refm(); // A::refm() , Self == @
(&A).refm(); // A::refm() , Self == *@
(&&A).refm(); // A::refm() , Self == **@
(&&&A).refm(); // &&&A::refm(), Self == @
}
所以,似乎,或多或少:
- 编译器将根据需要插入尽可能多的解引用运算符来调用方法。
- 编译器在解析使用
&self
声明的方法时(按引用调用):- 首先尝试调用
self
的单个取消引用
- 然后尝试调用
self
的确切类型
- 然后,尝试为匹配项插入尽可能多的解引用运算符
- 首先尝试调用
- 使用
self
(按值调用)类型T
声明的方法与使用&self
(按引用调用)类型 [=] 声明的方法一样17=] 并调用对点运算符左侧任何内容的引用。 - 首先使用原始内置解引用尝试上述规则,如果没有匹配,则使用具有
Deref
特征的重载。
确切的自动取消引用规则是什么?任何人都可以为这样的设计决定给出任何正式的理由吗?
您的伪代码非常正确。对于这个例子,假设我们有一个方法调用 foo.bar()
where foo: T
。我将使用 fully qualified syntax (FQS) 来明确调用方法的类型,例如A::bar(foo)
或 A::bar(&***foo)
。我只是要写一堆随机的大写字母,每个都是一些任意的 type/trait,除了 T
总是调用方法的原始变量 foo
的类型上。
算法的核心是:
- 对于每个"dereference step"
U
(即设置U = T
然后U = *T
, ...)- 如果有一个方法
bar
接收者类型(方法中self
的类型)完全匹配U
,使用它(a "by value method") - 否则,添加一个自动引用(接收者的
&
或&mut
),并且,如果某个方法的接收者匹配&U
,则使用它(an "autorefd method")
- 如果有一个方法
值得注意的是,一切都考虑了方法的“接收者类型”,不是特征的Self
类型,即impl ... for Foo { fn method(&self) {} }
考虑&Foo
匹配方法时,fn method2(&mut self)
匹配时会考虑&mut Foo
。
如果在内部步骤中有多个有效的特征方法是错误的(也就是说,在1.或2.中的每一个中只能有零个或一个有效的特征方法,但可以有一个有效的对于每个:首先采用 1 中的那个),并且固有方法优先于特征方法。如果我们到达循环末尾而没有找到任何匹配项,这也是一个错误。递归 Deref
实现也是错误的,这会使循环无限(它们将达到“递归限制”)。
这些规则在大多数情况下似乎都符合我的意思,尽管在某些边缘情况下能够编写明确的 FQS 形式非常有用,并且对于宏生成代码的合理错误消息也是如此。
只添加了一个自动引用,因为
- 如果没有界限,事情就会变得bad/slow,因为每种类型都可以有任意数量的引用
- 获取一个引用
&foo
保留与foo
的强连接(它是foo
本身的地址),但获取更多引用开始失去它:&&foo
是存储&foo
. 的堆栈上某个临时变量的地址
例子
假设我们有一个调用 foo.refm()
,如果 foo
的类型为:
X
,然后我们从U = X
开始,refm
的接收器类型为&...
,所以第 1 步不匹配,采用自动引用给我们&X
,这确实匹配(与Self = X
),所以调用是RefM::refm(&foo)
&X
,以U = &X
开头,在第一步匹配&self
(与Self = X
),所以调用是RefM::refm(foo)
&&&&&X
,这与任何一步都不匹配(&&&&X
或&&&&&X
未实现该特征),因此我们取消引用一次以获得U = &&&&X
, 匹配 1 (withSelf = &&&X
) 调用是RefM::refm(*foo)
Z
,在任何一步都不匹配,所以它被取消引用一次,得到Y
,它也不匹配,所以它再次被取消引用,得到X
,它不匹配 1,但在自动引用后匹配,所以调用是RefM::refm(&**foo)
.&&A
,1. 不匹配,2. 也不匹配,因为&A
(对于 1)或&&A
(对于 2)没有实现特征,所以它被取消引用到&A
,它匹配 1.,与Self = A
假设我们有 foo.m()
,而 A
不是 Copy
,如果 foo
有类型:
A
,然后U = A
直接匹配self
所以调用是M::m(foo)
和Self = A
&A
,那么 1. 不匹配,2. 也不匹配(&A
和&&A
都没有实现该特征),因此它被取消引用为A
,确实匹配,但是M::m(*foo)
需要按值取A
,因此移出foo
,因此出现错误。&&A
, 1. 不匹配,但 autorefing 给出&&&A
,确实匹配,所以调用是M::m(&foo)
和Self = &&&A
.
(此答案基于the code, and is reasonably close to the (slightly outdated) README。compiler/language这部分的主要作者Niko Matsakis也浏览了此答案。)
Rust 参考有 a chapter about the method call expression。我复制了下面最重要的部分。提醒:我们说的是表达式recv.m()
,其中recv
在下面称为"receiver expression"。
The first step is to build a list of candidate receiver types. Obtain these by repeatedly dereferencing the receiver expression's type, adding each type encountered to the list, then finally attempting an unsized coercion at the end, and adding the result type if that is successful. Then, for each candidate
T
, add&T
and&mut T
to the list immediately afterT
.For instance, if the receiver has type
Box<[i32;2]>
, then the candidate types will beBox<[i32;2]>
,&Box<[i32;2]>
,&mut Box<[i32;2]>
,[i32; 2]
(by dereferencing),&[i32; 2]
,&mut [i32; 2]
,[i32]
(by unsized coercion),&[i32]
, and finally&mut [i32]
.Then, for each candidate type
T
, search for a visible method with a receiver of that type in the following places:
T
's inherent methods (methods implemented directly onT
[¹]).- Any of the methods provided by a visible trait implemented by
T
. [...]
(关于 [¹] 的注意事项:我实际上认为这种措辞是错误的。I've opened an issue。让我们忽略括号中的那句话。)
让我们详细看一下您的代码中的几个示例!对于您的示例,我们可以忽略有关 "unsized coercion" 和 "inherent methods".
的部分(*X{val:42}).m()
:接收表达式的类型是i32
。我们执行这些步骤:
- 正在创建候选接收器类型列表:
i32
不能取消引用,所以我们已经完成了第 1 步。列表:[i32]
- 接下来,我们添加
&i32
和&mut i32
。列表:[i32, &i32, &mut i32]
- 正在为每个候选接收器类型搜索方法:
- 我们发现
<i32 as M>::m
具有接收器类型i32
。所以我们已经完成了。
- 我们发现
到目前为止很容易。现在我们挑一个比较难的例子:(&&A).m()
。接收者表达式的类型是 &&A
。我们执行这些步骤:
- 正在创建候选接收器类型列表:
&&A
可以取消引用&A
,因此我们将其添加到列表中。&A
可以再次取消引用,所以我们也将A
添加到列表中。A
无法取消引用,所以我们停止。列表:[&&A, &A, A]
- 接下来,对于列表中的每个类型
T
,我们在T
之后立即添加&T
和&mut T
。列表:[&&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
- 正在为每个候选接收器类型搜索方法:
- 没有接收器类型为
&&A
的方法,因此我们转到列表中的下一个类型。 - 我们发现方法
<&&&A as M>::m
确实有接收者类型&&&A
。这样我们就完成了。
- 没有接收器类型为
以下是所有示例的候选接收者列表。 ⟪x⟫
中的类型是 "won" 中的类型,即第一个可以找到拟合方法的类型。还请记住,列表中的第一个类型始终是接收者表达式的类型。最后,我将列表格式化为三行,但这只是格式化:这个列表是一个平面列表。
(*X{val:42}).m()
→<i32 as M>::m
[⟪i32⟫, &i32, &mut i32]
X{val:42}.m()
→<X as M>::m
[⟪X⟫, &X, &mut X, i32, &i32, &mut i32]
(&X{val:42}).m()
→<&X as M>::m
[⟪&X⟫, &&X, &mut &X, X, &X, &mut X, i32, &i32, &mut i32]
(&&X{val:42}).m()
→<&&X as M>::m
[⟪&&X⟫, &&&X, &mut &&X, &X, &&X, &mut &X, X, &X, &mut X, i32, &i32, &mut i32]
(&&&X{val:42}).m()
→<&&&X as M>::m
[⟪&&&X⟫, &&&&X, &mut &&&X, &&X, &&&X, &mut &&X, &X, &&X, &mut &X, X, &X, &mut X, i32, &i32, &mut i32]
(&&&&X{val:42}).m()
→<&&&X as M>::m
[&&&&X, &&&&&X, &mut &&&&X, ⟪&&&X⟫, &&&&X, &mut &&&X, &&X, &&&X, &mut &&X, &X, &&X, &mut &X, X, &X, &mut X, i32, &i32, &mut i32]
(&&&&&X{val:42}).m()
→<&&&X as M>::m
[&&&&&X, &&&&&&X, &mut &&&&&X, &&&&X, &&&&&X, &mut &&&&X, ⟪&&&X⟫, &&&&X, &mut &&&X, &&X, &&&X, &mut &&X, &X, &&X, &mut &X, X, &X, &mut X, i32, &i32, &mut i32]
(*X{val:42}).refm()
→<i32 as RefM>::refm
[i32, ⟪&i32⟫, &mut i32]
X{val:42}.refm()
→<X as RefM>::refm
[X, ⟪&X⟫, &mut X, i32, &i32, &mut i32]
(&X{val:42}).refm()
→<X as RefM>::refm
[⟪&X⟫, &&X, &mut &X, X, &X, &mut X, i32, &i32, &mut i32]
(&&X{val:42}).refm()
→<&X as RefM>::refm
[⟪&&X⟫, &&&X, &mut &&X, &X, &&X, &mut &X, X, &X, &mut X, i32, &i32, &mut i32]
(&&&X{val:42}).refm()
→<&&X as RefM>::refm
[⟪&&&X⟫, &&&&X, &mut &&&X, &&X, &&&X, &mut &&X, &X, &&X, &mut &X, X, &X, &mut X, i32, &i32, &mut i32]
(&&&&X{val:42}).refm()
→<&&&X as RefM>::refm
[⟪&&&&X⟫, &&&&&X, &mut &&&&X, &&&X, &&&&X, &mut &&&X, &&X, &&&X, &mut &&X, &X, &&X, &mut &X, X, &X, &mut X, i32, &i32, &mut i32]
(&&&&&X{val:42}).refm()
→<&&&X as RefM>::refm
[&&&&&X, &&&&&&X, &mut &&&&&X, ⟪&&&&X⟫, &&&&&X, &mut &&&&X, &&&X, &&&&X, &mut &&&X, &&X, &&&X, &mut &&X, &X, &&X, &mut &X, X, &X, &mut X, i32, &i32, &mut i32]
Y{val:42}.refm()
→<i32 as RefM>::refm
[Y, &Y, &mut Y, i32, ⟪&i32⟫, &mut i32]
Z{val:Y{val:42}}.refm()
→<i32 as RefM>::refm
[Z, &Z, &mut Z, Y, &Y, &mut Y, i32, ⟪&i32⟫, &mut i32]
A.m()
→<A as M>::m
[⟪A⟫, &A, &mut A]
(&A).m()
→<A as M>::m
[&A, &&A, &mut &A, ⟪A⟫, &A, &mut A]
(&&A).m()
→<&&&A as M>::m
[&&A, ⟪&&&A⟫, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
(&&&A).m()
→<&&&A as M>::m
[⟪&&&A⟫, &&&&A, &mut &&&A, &&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
A.refm()
→<A as RefM>::refm
[A, ⟪&A⟫, &mut A]
(&A).refm()
→<A as RefM>::refm
[⟪&A⟫, &&A, &mut &A, A, &A, &mut A]
(&&A).refm()
→<A as RefM>::refm
[&&A, &&&A, &mut &&A, ⟪&A⟫, &&A, &mut &A, A, &A, &mut A]
(&&&A).refm()
→<&&&A as RefM>::refm
[&&&A, ⟪&&&&A⟫, &mut &&&A, &&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
这个问题困扰了我很久,尤其是这部分:
(*X{val:42}).refm(); // i32::refm() , Self == @
X{val:42}.refm(); // X::refm() , Self == @
(&X{val:42}).refm(); // X::refm() , Self == *@
(&&X{val:42}).refm(); // &X::refm() , Self == *@
(&&&X{val:42}).refm(); // &&X::refm() , Self == *@
(&&&&X{val:42}).refm(); // &&&X::refm(), Self == *@
(&&&&&X{val:42}).refm(); // &&&X::refm(), Self == **@
直到我找到一种方法来记住这些奇怪的规则。我不确定这是否正确,但大多数时候这种方法是有效的。
关键是,在寻找使用哪个函数时,不要使用调用“点运算符”的类型来确定使用哪个“impl”,而是找到根据函数签名的函数,然后用函数签名.
判断“self”的类型我将函数定义代码转换如下:
trait RefM { fn refm(&self); }
impl RefM for i32 { fn refm(&self) { println!("i32::refm()"); } }
// converted to: fn refm(&i32 ) { println!("i32::refm()"); }
// => type of 'self' : i32
// => type of parameter: &i32
impl RefM for X { fn refm(&self) { println!("X::refm()"); } }
// converted to: fn refm(&X ) { println!("X::refm()"); }
// => type of 'self' : X
// => type of parameter: &X
impl RefM for &X { fn refm(&self) { println!("&X::refm()"); } }
// converted to: fn refm(&&X ) { println!("&X::refm()"); }
// => type of 'self' : &X
// => type of parameter: &&X
impl RefM for &&X { fn refm(&self) { println!("&&X::refm()"); } }
// converted to: fn refm(&&&X ) { println!("&&X::refm()"); }
// => type of 'self' : &&X
// => type of parameter: &&&X
impl RefM for &&&X { fn refm(&self) { println!("&&&X::refm()"); } }
// converted to: fn refm(&&&&X) { println!("&&&X::refm()"); }
// => type of 'self' : &&&X
// => type of parameter: &&&&X
因此,当您编写代码时:
(&X{val:42}).refm();
函数
fn refm(&X ) { println!("X::refm()");
会被调用,因为参数类型是&X
.
如果没有找到匹配的函数签名,则执行自动引用或某些自动取消引用。