什么是Rust的确切的自动引用规则?
我正在学习/试验Rust,在我用这种语言发现的所有优雅中,有一个让我感到困惑的特性,似乎完全不合适。
在进行方法调用时,Rust会自动解引用指针。 我做了一些testing,以确定确切的行为:
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 == **@ Am(); // 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 == @ }
所以,似乎或多或less:
- 编译器将根据需要插入尽可能多的解引用运算符来调用方法。
- 编译器在parsing使用
&self
(call-by-reference)声明的方法时:- 首先尝试呼吁对
self
进行一次解除 - 然后尝试调用
self
的确切types - 然后,尝试插入尽可能多的解除引用运算符来进行匹配
- 首先尝试呼吁对
- 使用types
T
self
(call-by-value)声明的方法就像使用&self
(call-by-reference)声明types为&T
并且调用点运算符左侧的引用。 - 上述规则首先使用原始的内置解引用来尝试,如果不匹配,则使用
Deref
特性的重载。
什么是确切的自动解除引用规则? 任何人都可以为这样的devise决定提供任何正式的理由吗?
你的伪代码是非常正确的。 对于这个例子,假设我们有一个方法调用foo.bar()
,其中foo: T
。 我将使用完全限定的语法 (FQS)来清楚地说明方法被调用的types,例如A::bar(foo)
或A::bar(&***foo)
。 我只是要写一堆随机的大写字母,每个都只是一些任意的types/特性,除了T
总是这个方法被调用的原始variablesfoo
的types。
algorithm的核心是:
- 对于每个“取消引用步骤”
U
(即设置U = T
,然后U = *T
,…)- 如果有一个方法
bar
,接收方types(方法中的self
types)与U
完全匹配,则使用它( “按值方法” ) - 否则,添加一个auto-ref(接收器的
&
或&mut
),如果某个方法的接收器匹配&U
,则使用它( 一个“autorefd方法” )
- 如果有一个方法
值得注意的是,一切都认为方法的“接收器types”, 而不是该特性的Self
types,即impl ... for Foo { fn method(&self) {} }
在匹配方法时考虑&Foo
,而fn method2(&mut self)
在匹配时会考虑&mut Foo
。
如果在内部步骤中有多个有效的trait方法是错误的(也就是说,在1或2中只能有零个或一个有效的特征方法,但是每个方法都可以有一个有效:从1开始),固有方法优先于特征方法。 如果我们到达循环的结尾而没有find任何匹配的东西,那也是一个错误。 recursion的Deref
实现也是一个错误,这使得循环无限(它们会达到“recursion限制”)。
这些规则似乎在大多数情况下都是这样做的,虽然有能力编写明确的FQS表单在一些边缘情况下非常有用,对于macros生成的代码也是非常有用的错误消息。
只有一个自动引用被添加,因为
- 如果没有绑定,事情变得很糟糕/缓慢,因为每个types可以有任意数量的引用
- 以一个引用
&foo
保留与foo
(它是foo
本身的地址)的强有力的联系,但是更多的开始丢失它:&&foo
是存储&foo
的栈上的一些临时variables的地址。
例子
假设我们有一个调用foo.refm()
,如果foo
types为:
-
X
,那么我们从U = X
开始,refm
有接收器types&...
,所以步骤1不匹配,以auto-ref给我们&X
,这匹配(与Self = X
),所以调用是RefM::refm(&foo)
-
&X
,从U = &X
,它与第一步(Self = X
)中的&self
匹配,所以调用是RefM::refm(foo)
-
&&&&&X
,这两个步骤都不匹配(该特征不适用于&&&&X
或&&&&&X
),因此我们取消引用一次以获得U = &&&&X
,匹配1(与Self = &&&X
),调用RefM::refm(*foo)
-
Z
,不匹配任何一步,所以它被解除引用一次,得到Y
,这也不匹配,所以它被再次解除,得到X
,这不匹配1,但在自动修复后匹配,所以调用是RefM::refm(&**foo)
。 -
&&A
,1.不匹配,因为这个特征没有针对&A
(针对1)或&&A
(针对2)实施,所以它被解除引用到&A
相匹配的&A
,其中Self = A
假设我们有foo.m()
,并且A
不是Copy
,如果foo
types为:
-
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
(这个答案是以代码为基础的 ,并且相当接近(稍微过时的)自述文件),这部分编译器/语言的主要作者Niko Matsakis也浏览了这个答案。)