我正在学习/试验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<'a> M for &'a X { fn m(self) { println!("&X::m()"); } }
impl<'a, 'b> M for &'a &'b X { fn m(self) { println!("&&X::m()"); } }
impl<'a, 'b, 'c> M for &'a &'b &'c 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<'a> RefM for &'a X { fn refm(&self) { println!("&X::refm()"); } }
impl<'a, 'b> RefM for &'a &'b X { fn refm(&self) { println!("&&X::refm()"); } }
impl<'a, 'b, 'c> RefM for &'a &'b &'c 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 }
}
struct A;
impl std::marker::Copy for A {}
impl M for A { fn m(self) { println!("A::m()"); } }
impl<'a, 'b, 'c> M for &'a &'b &'c A { fn m(self) { println!("&&&A::m()"); } }
impl RefM for A { fn refm(&self) { println!("A::refm()"); } }
impl<'a, 'b, 'c> RefM for &'a &'b &'c 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::refm() , 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 == **@
(*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 == **@
Y{val:42}.refm(); // i32::refm() , self == *@
Z{val:Y{val:42}}.refm(); // i32::refm() , self == **@
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
声明的方法时(call-by-reference):
self
self
self
(按值调用)为类型T
声明的方法就像使用&self
(按引用调用)声明类型&T
一样调用点运算符左侧的引用。Deref
特征的重载。什么是确切的自动解除引用规则?任何人都可以为这样的设计决定提供任何正式的理由吗?
答案 0 :(得分:110)
您的伪代码非常正确。对于此示例,假设我们有一个方法调用foo.bar()
,其中foo: T
。我将使用fully qualified syntax(FQS)明确指出调用方法的类型,例如: A::bar(foo)
或A::bar(&***foo)
。我只是要写一堆随机大写字母,每一个都只是一些任意类型/特征,除了T
始终是调用该方法的原始变量foo
的类型上。
算法的核心是:
U
(即设置U = T
然后设置U = *T
,...)
bar
中接收器类型(方法中self
的类型)与U
完全匹配,请使用它(a "by value method")< / LI>
&
或&mut
接收器),如果某个方法的接收器与&U
匹配,则使用它({{ 3}})值得注意的是,一切都考虑了&#34;接收器类型&#34;方法,不特征的Self
类型,即impl ... for Foo { fn method(&self) {} }
在匹配方法时考虑&Foo
,而fn method2(&mut self)
会考虑匹配时&mut Foo
。
如果在内部步骤中有多个特征方法有效,那么这是一个错误(也就是说,在1.或2.每个中只有零个或一个特征方法有效,但是可以有一个对每个都有效:1中的一个将首先被采用,并且固有方法优先于特征方法。如果我们到达循环的末尾而没有找到匹配的东西,那也是一个错误。具有递归Deref
实现也是错误的,这使得循环无限(它们会点击&#34;递归限制&#34;)。
这些规则似乎在大多数情况下都是这样做的,尽管能够编写明确的FQS表单在某些边缘情况下非常有用,并且对于宏生成的代码也是非常有用的。
只添加了一个自动引用,因为
&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
未实现特征),因此我们取消引用一次以获得{{1} },与1匹配(U = &&&&X
),调用为Self = &&&X
RefM::refm(*foo)
,不会匹配任何一个步骤,因此会被解除引用一次,以获得Z
,这也不匹配,因此它再次被取消引用,获取Y
,其中不匹配1,但在自动恢复后匹配,因此呼叫为X
。RefM::refm(&**foo)
,1。不匹配,2也不匹配,因为&&A
(1)或&A
(2)没有实现特征,所以它被取消引用&&A
,其匹配1.,&A
如果Self = A
有类型,则假设我们有foo.m()
,而A
不是Copy
:
foo
,然后A
直接与U = A
匹配,因此通过self
M::m(foo)
Self = A
,然后1.不匹配,2也不匹配(&A
和&A
都没有实现特征),所以它被解除引用{{1 }},匹配,但&&A
要求按值A
移出M::m(*foo)
,因此错误。A
,1。无法匹配,但自动归档会提供匹配的foo
,因此呼叫为&&A
&&&A
。(此答案基于an "autorefd method"和the code。编辑/语言的这一部分的主要作者Niko Matsakis也浏览了这个答案。)
答案 1 :(得分:2)
Rust参考具有a chapter about the method call expression。我在下面复制了最重要的部分。提醒:我们正在谈论表达式recv.m()
,其中recv
在下面称为“接收者表达式”。
第一步是建立候选接收者类型的列表。通过重复引用接收方表达式的类型,将遇到的每种类型添加到列表中,然后最后尝试在结尾处尝试不定大小的强制,然后添加结果类型(如果成功)来获取这些内容。然后,对于每个候选人
T
,在&T
之后立即将&mut T
和T
添加到列表中。例如,如果接收者的类型为
Box<[i32;2]>
,则候选类型将为Box<[i32;2]>
,&Box<[i32;2]>
,&mut Box<[i32;2]>
,[i32; 2]
(通过取消引用) ,&[i32; 2]
,&mut [i32; 2]
,[i32]
(强制大小调整),&[i32]
,最后是&mut [i32]
。然后,对于每种候选类型
T
,在以下位置使用该类型的接收器搜索可见的方法:
T
的固有方法(直接在T
[¹]上实现的方法)。T
实现的可见特征提供的任何方法。 [...]
(关于[¹] 的说明:我实际上认为此措词是错误的。I've opened an issue。我们只需忽略括号中的那句话。)
让我们详细阅读代码中的一些示例!对于您的示例,我们可以忽略有关“不定大小强制”和“固有方法”的部分。
(*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
之后立即添加&mut T
和T
。列表: [&&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
&&A
的方法,因此我们转到列表中的下一个类型。<&&&A as M>::m
确实具有接收者类型&&&A
。我们完成了。以下是所有示例的候选收件人列表。 ⟪x⟫
中包含的类型是“获胜”的类型,即可以找到拟合方法的第一种类型。还请记住,列表中的第一个类型始终是接收方表达式的类型。最后,我将列表格式化为三行,但这只是格式化:此列表是平面列表。
(*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]
答案 2 :(得分:0)
我被这个问题困扰了很久,特别是这部分:
(*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
。
如果没有找到匹配的函数签名,则执行自动引用或某些自动取消引用。