gpt4 book ai didi

reference - Rust的确切自动引用规则是什么?

转载 作者:行者123 更新时间:2023-11-29 08:32:52 25 4
gpt4 key购买 nike

我正在学习/试验生锈,在我在这门语言中发现的所有优雅中,有一个特点让我困惑,似乎完全不合适。
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 == @
}

Playground
所以,看起来,或多或少:
编译器将根据需要插入尽可能多的解引用运算符来调用方法。
编译器在解析使用 &self声明的方法时(通过引用调用):
第一次尝试调用 self
然后尝试调用精确类型的 self
然后,尝试插入匹配所需的尽可能多的解引用运算符
对于类型 self使用 T(按值调用)声明的方法的行为与对于类型 &self使用 &T(按引用调用)声明并在对dot运算符左侧任何内容的引用上调用它们的行为类似。
上面的规则首先使用原始的内置解引用进行尝试,如果没有匹配,则使用带有 Dereftrait的重载。
自动解引用的具体规则是什么?有谁能给出这样一个设计决策的正式理由吗?

最佳答案

你的伪代码非常正确。对于这个例子,假设我们有一个方法调用foo.bar()wherefoo: T。我将使用fully qualified syntax(FQS)来明确调用方法的类型,例如A::bar(foo)A::bar(&***foo)。我要写一堆随机的大写字母,每一个都是一些任意的类型/特征,除了T总是调用方法的原始变量的类型。
算法的核心是:
对于每个"dereference step"foo(即设置U,然后设置U = T,…)
如果有一个方法U = *T其中接收器类型(方法中bar的类型)与self完全匹配,请使用它(a "by value method"
否则,添加一个auto ref(接受接收器的U&),如果某个方法的接收器匹配&mut,则使用它(an "autorefd method"
值得注意的是,每件事都考虑方法的“接收者类型”,而不是特征的&U类型,即Self在匹配方法时考虑impl ... for Foo { fn method(&self) {} },而&Foo在匹配时考虑fn method2(&mut self)
如果在内部步骤中存在多个有效的trait方法(也就是说,在1的每个步骤中只能有零个或一个有效的trait方法),则这是一个错误。或者2,但是每个方法都有一个有效的:1中的一个优先),并且固有方法优先于特征方法。如果在循环结束时没有找到任何匹配的内容,这也是一个错误。递归&mut Foo实现也是一个错误,它使循环无限(它们将达到“递归限制”)。
这些规则在大多数情况下似乎都是“我的意思”,尽管在某些边缘情况下,能够编写明确的FQS表单非常有用,对于宏生成的代码来说,这些规则也非常有用。
只添加了一个自动引用,因为
如果没有绑定,事情就会变得糟糕/缓慢,因为每种类型都可以有任意数量的引用
引用一个Deref会保留与&foo的强连接(它是foo本身的地址),但如果引用更多则会失去它:foo是堆栈上存储&&foo的某个临时变量的地址。
实例
假设我们有一个调用&foo,如果foo.refm()具有类型:
foo,然后我们从X开始,U = X具有接收器类型refm,因此步骤1不匹配,采用自动ref将给出&...,并且这确实匹配(与&X匹配),因此调用是Self = X
RefM::refm(&foo),以&X开头,这与第一步中的U = &X匹配(以&self开头),因此调用是Self = X
RefM::refm(foo),这与两个步骤都不匹配(trait不是为&&&&&X&&&&X实现的),因此我们取消引用一次以获取&&&&&X,它与1匹配(与U = &&&&X匹配),调用是Self = &&&X
RefM::refm(*foo)与任一步骤都不匹配,因此它被取消引用一次,得到也不匹配的Z,因此它被再次取消引用,得到不匹配1的Y,但在自动定义后匹配,因此调用是X
RefM::refm(&**foo),1。不匹配,2也不匹配。由于该特征不是针对&&A(for 1)或&A(for 2)实现的,因此它被解引用为&&A,它与1.和&A匹配
假设我们有Self = A,而foo.m()不是A,如果Copy有类型:
foo,然后A直接与U = A匹配,因此呼叫是selfM::m(foo)
Self = A,然后1。不匹配,2也不匹配。(既非&A也非&A实现特征),因此它被解引用到&&A,它确实匹配,但A要求按值取M::m(*foo),因此移出A,因此产生错误。
foo,1。不匹配,但自动定义给出了&&A,这是匹配的,因此调用是&&&AM::m(&foo)的。
(此答案基于the codeis reasonably close to the (slightly outdated) README。这部分编译器/语言的主要作者Niko Matsakis也浏览了一下这个答案。)

关于reference - Rust的确切自动引用规则是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48070460/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com