当前位置: 首页 > 知识库问答 >
问题:

Rust 的确切自动取消引用规则是什么?

李兴为
2023-03-14

我正在学习/试验 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 == @
}

(游乐场)

所以,似乎或多或少:

  • 编译器将插入调用方法所需的尽可能多的解引用运算符
  • 编译器在解析使用<code>声明的方法时

确切的自动取消引用规则是什么?有人能给出这样一个设计决策的正式理由吗?

共有3个答案

公西财
2023-03-14

这个问题困扰了我很久,尤其是对于这一部分:

    (*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 == **@

直到我找到一种方法来记住这些奇怪的规则。我不确定这是否正确,但大多数时候这种方法是有效的。

关键是,在寻找要使用的函数时,不要使用调用“点运算符”的类型来确定要使用的“impl”,而是根据函数签名找到函数,然后用函数签名确定“self”的类型。

我将函数定义代码转换如下:

trait RefM { fn refm(&self); }

impl RefM for i32  { fn refm(&self) { println!("i32::refm()");  } }
// converted to:     fn refm(&i32 ) { println!("i32::refm()");  }
// => type of  'self'  : i32
// => type of parameter: &i32

impl RefM for X    { fn refm(&self) { println!("X::refm()");    } }
// converted to:     fn refm(&X   ) { println!("X::refm()");    }
// => type of  'self'  : X
// => type of parameter: &X

impl RefM for &X   { fn refm(&self) { println!("&X::refm()");   } }
// converted to:     fn refm(&&X  ) { println!("&X::refm()");   }
// => type of  'self'  : &X
// => type of parameter: &&X

impl RefM for &&X  { fn refm(&self) { println!("&&X::refm()");  } }
// converted to:     fn refm(&&&X ) { println!("&&X::refm()");  }
// => type of  'self'  : &&X
// => type of parameter: &&&X

impl RefM for &&&X { fn refm(&self) { println!("&&&X::refm()"); } }
// converted to:     fn refm(&&&&X) { println!("&&&X::refm()"); }
// => type of  'self'  : &&&X
// => type of parameter: &&&&X

因此,当您编写代码时:

(

函数

<代码>fn refm(

因为参数类型是< code >

如果没有找到匹配的函数签名,则执行自动引用或一些自动deref。

归鸿朗
2023-03-14

Rust参考有一章是关于方法调用表达式的。我在下面复制了最重要的部分。提醒:我们正在谈论一个表达式recv. m(),其中recv在下面称为“接收器表达式”。

第一步是建立候选接收器类型的列表。通过重复取消引用接收方表达式的类型,将遇到的每个类型添加到列表中,然后最后尝试一个未调整大小的强制,如果成功,则添加结果类型。然后,对于每个候选< code>T,添加< code >

例如,如果接收方有< code >框

然后,对于每个候选类型 T,在以下位置搜索具有该类型接收器的可见方法:

  1. T的固有方法(直接在T[CharStyle]上实现的方法)
  2. T实现的可见特征提供的任何方法。[…]

(关于[1]的注释:我实际上认为这句话是错误的。我提出了一个问题。让我们忽略括号中的那句话。)

让我们详细看一下代码中的几个示例!对于您的示例,我们可以忽略有关“未放大的强制”和“固有方法”的部分。

< code>(*X{val:42})。m():接收方表达式的类型是< code>i32。我们执行以下步骤:

  • 创建候选接收器类型列表:
    • i32无法取消引用,因此我们已经完成了第1步。列表:[i32]
    • 接下来,我们添加
    • 我们发现<code>


    这是一个很好的例子

    到目前为止很容易。现在让我们选择一个更难的例子:

    • 正在创建候选接收器类型列表:
      • 没有接收器类型方法

      以下是所有示例的候选接收器列表。包含在<code>中的类型⟪x⟫是“赢”的类型,即可以找到拟合方法的第一种类型。还要记住,列表中的第一个类型始终是接收方表达式的类型。最后,我将列表格式化为三行,但这只是格式化:这个列表是一个平面列表。

      • (*X{val:42}).m()<i32 as M>::m
        [⟪i32⟫, &i32, &mut i32]
        
        [⟪X⟫, &X, &mut X, 
         i32, &i32, &mut i32]
        
        [⟪&X⟫, &&X, &mut &X, 
         X, &X, &mut X, 
         i32, &i32, &mut i32]
        
        [⟪&&X⟫, &&&X, &mut &&X, 
         &X, &&X, &mut &X, 
         X, &X, &mut X, 
         i32, &i32, &mut i32]
        
        [⟪&&&X⟫, &&&&X, &mut &&&X, 
         &&X, &&&X, &mut &&X, 
         &X, &&X, &mut &X, 
         X, &X, &mut X, 
         i32, &i32, &mut i32]
        
        [&&&&X, &&&&&X, &mut &&&&X, 
         ⟪&&&X⟫, &&&&X, &mut &&&X, 
         &&X, &&&X, &mut &&X, 
         &X, &&X, &mut &X, 
         X, &X, &mut X, 
         i32, &i32, &mut i32]
        
        [&&&&&X, &&&&&&X, &mut &&&&&X, 
         &&&&X, &&&&&X, &mut &&&&X, 
         ⟪&&&X⟫, &&&&X, &mut &&&X, 
         &&X, &&&X, &mut &&X, 
         &X, &&X, &mut &X, 
         X, &X, &mut X, 
         i32, &i32, &mut i32]
        
        • (*X{val:42}).refm()<i32 as RefM>::refm
          [i32, ⟪&i32⟫, &mut i32]
          
          [X, ⟪&X⟫, &mut X, 
           i32, &i32, &mut i32]
          
          [⟪&X⟫, &&X, &mut &X, 
           X, &X, &mut X, 
           i32, &i32, &mut i32]
          
          [⟪&&X⟫, &&&X, &mut &&X, 
           &X, &&X, &mut &X, 
           X, &X, &mut X, 
           i32, &i32, &mut i32]
          
          [⟪&&&X⟫, &&&&X, &mut &&&X, 
           &&X, &&&X, &mut &&X, 
           &X, &&X, &mut &X, 
           X, &X, &mut X, 
           i32, &i32, &mut i32]
          
          [⟪&&&&X⟫, &&&&&X, &mut &&&&X, 
           &&&X, &&&&X, &mut &&&X, 
           &&X, &&&X, &mut &&X, 
           &X, &&X, &mut &X, 
           X, &X, &mut X, 
           i32, &i32, &mut i32]
          
          [&&&&&X, &&&&&&X, &mut &&&&&X, 
           ⟪&&&&X⟫, &&&&&X, &mut &&&&X, 
           &&&X, &&&&X, &mut &&&X, 
           &&X, &&&X, &mut &&X, 
           &X, &&X, &mut &X, 
           X, &X, &mut X, 
           i32, &i32, &mut i32]
          
          • Y{val:42}.refm()<i32 as RefM>::refm
            [Y, &Y, &mut Y,
             i32, ⟪&i32⟫, &mut i32]
            
            [Z, &Z, &mut Z,
             Y, &Y, &mut Y,
             i32, ⟪&i32⟫, &mut i32]
            
            • A.m()<A as M>::m
              [⟪A⟫, &A, &mut A]
              
              [&A, &&A, &mut &A,
               ⟪A⟫, &A, &mut A]
              
              [&&A, ⟪&&&A⟫, &mut &&A,
               &A, &&A, &mut &A,
               A, &A, &mut A]
              
              [⟪&&&A⟫, &&&&A, &mut &&&A,
               &&A, &&&A, &mut &&A,
               &A, &&A, &mut &A,
               A, &A, &mut A]
              
              [A, ⟪&A⟫, &mut A]
              
              [⟪&A⟫, &&A, &mut &A,
               A, &A, &mut A]
              
              [&&A, &&&A, &mut &&A,
               ⟪&A⟫, &&A, &mut &A,
               A, &A, &mut A]
              
              [&&&A, ⟪&&&&A⟫, &mut &&&A,
               &&A, &&&A, &mut &&A,
               &A, &&A, &mut &A,
               A, &A, &mut A]
              

邢雨华
2023-03-14

你的伪代码非常正确。对于这个例子,假设我们有一个方法调用< code>foo.bar(),其中< code>foo: T。我将使用完全限定语法(FQS)来明确调用方法的类型,例如< code>A::bar(foo)或< code>A::bar(

该算法的核心是:

    < li >对于每个“取消引用步骤”< code>U(即,设置< code>U = T,然后设置< code>U = *T,...) < ol > < li >如果有一个方法< code>bar,其中接收方类型(方法中< code>self的类型)与< code>U完全匹配,请使用它(一种“按值方法”) < li >否则,添加一个自动参考(取< code >

值得注意的是,所有内容都考虑了方法的“接收者类型”,而不是特征的Self类型,即Foo{fn方法的impl(

如果在内部步骤中有多个有效的trait方法,这是一个错误(也就是说,在每个1中只能有零个或一个有效的trait方法。或者2。,但是每一个都可以有一个有效的方法:首先取1中的方法),并且固有方法优先于特征方法。如果我们到达循环的末尾却没有找到任何匹配的东西,这也是一个错误。拥有递归的< code>Deref实现也是错误的,这会使循环无限循环(它们将达到“递归极限”)。

这些规则似乎在大多数情况下都是有意义的,尽管在某些极端情况下,以及对于宏生成代码的合理错误消息,能够编写明确的FQS形式是非常有用的。

仅添加一个自动引用,因为:

  • 如果没有绑定,事情会变得糟糕/缓慢,因为每种类型都可以获取任意数量的引用
  • 引用一个引用<代码>

假设我们有一个调用<code>foo。refm(),如果foo具有以下类型:

    < li> X,那么我们从< code>U = X开始,< code>refm的接收器类型为< code >

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

    < li> A,则< code>U = A直接匹配< code>self,因此调用为< code > M::M(foo) with < code > Self = A < li > <代码>

(这个答案是基于代码的,并且相当接近(略微过时的)自述文件。Niko Matsakis,这部分编译器/语言的主要作者,也瞥了一眼这个答案。

 类似资料:
  • 问题内容: 我是GoLang的新手,来自Delphi C ++世界-诚然对这种语言感到非常兴奋,我认为它将成为“下一件大事”。 我正在尝试了解Go解析器和编译器如何处理指针和引用-似乎找不到任何放置一些明确规则的地方。 例如,在下面的代码示例中,返回类型和局部变量是指针类型,并且在其声明中需要使用指针符号,但是在使用时不必取消引用它们:。但是在同一代码中,输入参数被声明为指针,并且必须被取消引用才

  • 以上两段代码,一个返回String的可变引用,一个返回不可变引用,为什么返回可变引用的闭包实现了FnOnce,返回不可变引用的闭包只实现了Fn呢? rust返回引用时,遵循什么规则?和my_s_ref本身有关还是my_string有关?

  • 问题内容: 好吧,首先我应该问一下这是否与浏览器有关。 我已经读到,如果找到了无效的令牌,但是代码段在该无效令牌之前一直有效,如果在该令牌之前加了换行符,则在该令牌之前插入一个分号。 但是,引用由分号插入引起的错误的常见示例是: ..似乎不遵循此规则,因为_a是有效令牌。 另一方面,分解呼叫链可以按预期工作: 是否有人对规则有更深入的描述? 问题答案: 首先,您应该知道哪些语句受自动分号插入(为简

  • 团队, 使用continueAsNew的确切用例是什么? 由于我们支持Cron附表进行定期活动,我不知道使用它的场景。 我们这样做是为了提供向后兼容性吗

  • 那么,换句话说,为什么编译器不能在第二个方法中执行自动装箱呢?是因为在第二个方法中,绑定不是显式的,而在第一个方法中绑定是明确的。

  • 使用指南 - 统计设置 - 统计规则设置 - 什么是统计规则 统计规则指对网站流量数据的分类或者过滤方法。如果您对获取的流量数据有特殊需求,一般可以通过设置统计规则的方式来实现。 例如,如果您希望获取的流量数据中不包含公司内部访问的流量,那么您可以通过排除IP的方式实现;如果您希望按照实际业务内容划分流量数据,那么您可以通过新增子目录的方式实现。