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

标签 reference dereference formal-semantics 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 == @
}

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/

相关文章:

php - 意外观察 : var_dump() of an array is flagging referenced elements. .. 从什么时候开始?

c - 取消引用 ptread_create 使用的结构中的数组

c - 解引用指向不完整类型的指针 - 使用指向函数的指针将值分配给结构

c++ - 指向成员的指针的语法糖适用于数组但不适用于 std::vector

programming-languages - 操作语义、指称语义和公理语义之间有什么区别?

javascript - 为什么在 for.Each 中处理后数组中的值没有改变?

javascript - 我很难通过 Javascript 引用正确的 DOM 节点。有什么问题吗?

c++ - C++ 中的引用初始化

formal-methods - 循环不变量和最弱前置条件有什么关系

haskell - 指称语义映射是可判定的吗?