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

标签 rust reference language-lawyer

我正在使用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(按引用的引用)声明,并引用点运算符左侧的内容进行调用。
  • 上面的规则首先使用原始的内置解引用进行尝试,如果不匹配,则使用Deref特性的重载。

  • 确切的自动取消引用规则是什么?任何人都可以对这样的设计决定给出任何正式的理由吗?

    最佳答案

    您的伪代码几乎是正确的。对于此示例,假设我们有一个方法叫做foo.bar(),其中foo: T。我将使用fully qualified syntax(FQS)来明确说明调用该方法的类型,例如A::bar(foo)A::bar(&***foo)。我只是要写一堆随机的大写字母,每个字母都是任意的类型/特征,除了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格式的能力在某些极端情况下以及对于宏生成代码的明智错误消息非常有用。
    仅添加一个自动引用,因为
  • 如果没有界限,事情就会变糟/变慢,因为每种类型可以有任意数量的引用
  • 使用一个引用&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(带有Self = &&&X),并且调用为RefM::refm(*foo)
  • Z,与任一步骤均不匹配,因此将其取消引用一次,以获取Y,它也与之不匹配,因此再次对其进行引用,以获取X,其不匹配1,但在自动引用后匹配,因此调用是RefM::refm(&**foo)
  • &&A,1.不匹配,也不匹配2。因为trait尚未为&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.不匹配,但是自动引用会给出匹配的&&&A,因此调用是M::m(&foo)Self = &&&A

  • (此答案基于the codeis reasonably close to the (slightly outdated) README。编译器/语言的这一部分的主要作者Niko Matsakis也浏览了此答案。)

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

    相关文章:

    rust - Rust 中的组合运算符和管道转发运算符

    recursion - 如何使用 Read::take 递归地读取具有通用 Read 特征的树结构?

    rust - 是否有更简单的方法来获取 Actix-Web HTTP header 的字符串值?

    c++ - 常量引用 - C++

    c++ - 为什么在已删除的默认构造函数旁边定义一个空拷贝构造函数会使空列表的值初始化失败?

    c++ - 当它是 noop 时是否需要调用一个非平凡的析构函数?

    rust - &* 在 Rust 中结合在一起做什么?

    c++ - 用引用隐藏指针

    javascript - 这。引用对象而不是窗口对象

    c - 指向结构指针的指针是否具有隐含的可互换性?