Rust 的确切自动解引用规则是什么? [英] What are Rust's exact auto-dereferencing rules?

查看:15
本文介绍了Rust 的确切自动解引用规则是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在学习/试验 Rust,在我发现这种语言的所有优雅中,有一个特殊性让我感到困惑并且似乎完全不合适.

I'm learning/experimenting with Rust, and in all the elegance that I find in this language, there is one peculiarity that baffles me and seems totally out of place.

Rust 在进行方法调用时会自动取消引用指针.我做了一些测试来确定确切的行为:

Rust automatically dereferences pointers when making method calls. I made some tests to determine the exact behaviour:

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 == @
}

(游乐场)

所以,似乎,或多或少:

So, it seems that, more or less:

  • 编译器将根据调用方法插入尽可能多的解引用运算符.
  • 编译器,在解析使用 &self(按引用调用)声明的方法时:
    • 首先尝试调用对 self
    • 的单个取消引用
    • 然后尝试调用self
    • 的确切类型
    • 然后,尝试插入尽可能多的取消引用运算符以进行匹配
    • The compiler will insert as many dereference operators as necessary to invoke a method.
    • The compiler, when resolving methods declared using &self (call-by-reference):
      • First tries calling for a single dereference of self
      • Then tries calling for the exact type of self
      • Then, tries inserting as many dereference operators as necessary for a match

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

      What are the exact auto-dereferencing rules? Can anyone give any formal rationale for such a design decision?

      推荐答案

      您的伪代码非常正确.对于这个例子,假设我们有一个方法调用 foo.bar() 其中 foo: T.我将使用 完全限定语法 (FQS) 明确说明调用方法的类型,例如A::bar(foo)A::bar(&***foo).我只是要写一堆随机的大写字母,每个字母都只是一些任意的类型/特征,除了 T 始终是原始变量 foo 的类型方法被调用.

      Your pseudo-code is pretty much correct. For this example, suppose we had a method call foo.bar() where foo: T. I'm going to use the fully qualified syntax (FQS) to be unambiguous about what type the method is being called with, e.g. A::bar(foo) or A::bar(&***foo). I'm just going to write a pile of random capital letters, each one is just some arbitrary type/trait, except T is always the type of the original variable foo that the method is called on.

      算法的核心是:

      • For each "dereference step" U (that is, set U = T and then U = *T, ...)
      1. 如果有一个方法 bar 的接收者类型(方法中 self 的类型)与 U 完全匹配,则使用它(一种按值法"/a>)
      2. 否则,添加一个自动引用(取接收器的 &&mut),并且,如果某个方法的接收器匹配 &U,使用它(自动引用方法")
      1. if there's a method bar where the receiver type (the type of self in the method) matches U exactly , use it (a "by value method")
      2. otherwise, add one auto-ref (take & or &mut of the receiver), and, if some method's receiver matches &U, use it (an "autorefd method")

    • 值得注意的是,一切都考虑了接收器类型".的方法,不是特征的Self类型,即impl ... for Foo { fn method(&self) {} } 匹配方法时考虑&Foo,匹配时fn method2(&mut self) 匹配时考虑&mut Foo.

      Notably, everything considers the "receiver type" of the method, not the Self type of the trait, i.e. impl ... for Foo { fn method(&self) {} } thinks about &Foo when matching the method, and fn method2(&mut self) would think about &mut Foo when matching.

      如果在内部步骤中有多个有效的 trait 方法(也就是说,在 1. 或 2. 中的每一个中只能有零个或一个有效的 trait 方法,但对于每个可以有一个有效的 trait 方法),这是一个错误: 将首先采用 1 中的那个),并且固有方法优先于特征方法.如果我们在没有找到任何匹配的情况下到达循环末尾,这也是一个错误.具有递归 Deref 实现也是一个错误,这会使循环无限(它们会达到递归限制").

      It is an error if there's ever multiple trait methods valid in the inner steps (that is, there can be only be zero or one trait methods valid in each of 1. or 2., but there can be one valid for each: the one from 1 will be taken first), and inherent methods take precedence over trait ones. It's also an error if we get to the end of the loop without finding anything that matches. It is also an error to have recursive Deref implementations, which make the loop infinite (they'll hit the "recursion limit").

      这些规则在大多数情况下似乎是我的意思,尽管能够编写明确的 FQS 表单在某些边缘情况下非常有用,并且对于宏生成的代码的合理错误消息.

      These rules seem to do-what-I-mean in most circumstances, although having the ability to write the unambiguous FQS form is very useful in some edge cases, and for sensible error messages for macro-generated code.

      只添加了一个自动引用,因为

      Only one auto-reference is added because

      • 如果没有限制,事情就会变得糟糕/缓慢,因为每种类型都可以有任意数量的引用
      • 获取一个引用 &foo 保留了与 foo 的强连接(它是 foo 本身的地址),但需要更多的开始丢失它:&&foo 是堆栈上存储 &foo 的某个临时变量的地址.
      • if there was no bound, things get bad/slow, since every type can have an arbitrary number of references taken
      • taking one reference &foo retains a strong connection to foo (it is the address of foo itself), but taking more starts to lose it: &&foo is the address of some temporary variable on the stack that stores &foo.

      假设我们有一个调用 foo.refm(),如果 foo 有类型:

      Suppose we have a call foo.refm(), if foo has type:

      • 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. 也不匹配,因为 &A(对于 1)或&&A(对于 2),所以它被解引用到 &A,它匹配 1.,Self = A
      • X, then we start with U = X, refm has receiver type &..., so step 1 doesn't match, taking an auto-ref gives us &X, and this does match (with Self = X), so the call is RefM::refm(&foo)
      • &X, starts with U = &X, which matches &self in the first step (with Self = X), and so the call is RefM::refm(foo)
      • &&&&&X, this doesn't match either step (the trait isn't implemented for &&&&X or &&&&&X), so we dereference once to get U = &&&&X, which matches 1 (with Self = &&&X) and the call is RefM::refm(*foo)
      • Z, doesn't match either step so it is dereferenced once, to get Y, which also doesn't match, so it's dereferenced again, to get X, which doesn't match 1, but does match after autorefing, so the call is RefM::refm(&**foo).
      • &&A, the 1. doesn't match and neither does 2. since the trait is not implemented for &A (for 1) or &&A (for 2), so it is dereferenced to &A, which matches 1., with Self = A

      假设我们有 foo.m(),并且 A 不是 Copy,如果 foo有类型:

      Suppose we have foo.m(), and that A isn't Copy, if foo has type:

      • A,然后 U = A 直接匹配 self 所以调用是 M::m(foo) with Self = A
      • &A,那么 1. 不匹配,2. 也不匹配(&A&&Acode> 实现 trait),所以它被取消引用到 A,它确实匹配,但是 M::m(*foo) 需要取 A> 按值,因此移出 foo,因此出现错误.
      • &&A, 1. 不匹配,但自动引用给出了 &&&A,它确实匹配,所以调用是 M::m(&foo) with Self = &&&A.
      • A, then U = A matches self directly so the call is M::m(foo) with Self = A
      • &A, then 1. doesn't match, and neither does 2. (neither &A nor &&A implement the trait), so it is dereferenced to A, which does match, but M::m(*foo) requires taking A by value and hence moving out of foo, hence the error.
      • &&A, 1. doesn't match, but autorefing gives &&&A, which does match, so the call is M::m(&foo) with Self = &&&A.

      (此答案基于 代码是相当接近(稍微过时的)自述文件.编译器/语言这部分的主要作者 Niko Matsakis 也浏览了这个答案.)

      (This answer is based on the code, and is reasonably close to the (slightly outdated) README. Niko Matsakis, the main author of this part of the compiler/language, also glanced over this answer.)

      这篇关于Rust 的确切自动解引用规则是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆