【问题标题】:What are Rust's exact auto-dereferencing rules?Rust 的确切自动解除引用规则是什么?
【发布时间】:2015-02-14 20:31:55
【问题描述】:

我正在学习/试验 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 trait 的重载。

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

【问题讨论】:

  • 我已将其交叉发布到Rust subreddit,希望得到一些好的答案!
  • 为了获得更多乐趣,请尝试在仿制药中重复实验并比较结果。

标签: rust reference language-lawyer


【解决方案1】:

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

算法的核心是:

  • 对于每个"dereference step"U(即设置U = T,然后设置U = *T,...)
    1. 如果有一个方法bar 的接收者类型(方法中self 的类型)与U 完全匹配,请使用它(a "by value method"
    2. 否则,添加一个自动引用(以接收者的&&mut),如果某些方法的接收者与&U 匹配,则使用它(an "autorefd method"

值得注意的是,一切都考虑方法的“接收者类型”,不是特征的Self类型,即impl ... for Foo { fn method(&self) {} }在匹配方法时考虑&Foo,而@987654347 @在匹配时会考虑&mut Foo

如果在内部步骤中有多个有效的 trait 方法(也就是说,在 1. 或 2. 中的每一个中只能有 0 个或一个有效的 trait 方法,但每个可以有一个有效的 trait 方法),这是一个错误:从 1 开始的那一个将被首先采用),并且固有方法优先于 trait 方法。如果我们在循环结束时没有找到任何匹配的东西,这也是一个错误。使用递归 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,但匹配在 autorefing 之后,调用是 RefM::refm(&**foo)
  • &&A,1. 不匹配,2. 也不匹配,因为 &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 都没有实现 trait),所以它被取消引用到 A,它确实匹配,但 M::m(*foo) 需要采取A 按值计算,因此移出 foo,因此出现错误。
  • &&A, 1. 不匹配,但 autorefing 给出了匹配的 &&&A,所以调用是 M::m(&foo)Self = &&&A

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

【讨论】:

  • 这个答案似乎详尽而详细,但我认为它缺乏简短且易于理解的规则摘要。在comment by Shepmaster 中给出了这样一个总结:“它 [deref 算法] 将尽可能多次地 deref (&&String -> &String -> String -> str),然后以最大值引用一次 (str -> &str)"。
  • (我自己不知道这个解释有多准确和完整。)
  • 在什么情况下会发生自动取消引用?它仅用于方法调用的接收者表达式吗?还用于字段访问?赋值右手边?左手边?函数参数?返回值表达式?
  • 注意:目前,nomicon 有一个 TODO 注释可以从这个答案中窃取信息并将其写入static.rust-lang.org/doc/master/nomicon/dot-operator.html
  • 强制是 (A) 在此之前尝试还是 (B) 在此之后尝试或 (C) 在此算法的每个步骤中尝试或 (D) 其他方法?
【解决方案2】:

Rust 引用有 a chapter about the method call expression。我复制了下面最重要的部分。提醒:我们说的是一个表达式recv.m(),下面recv被称为“接收者表达式”。

第一步是建立一个候选接收器类型列表。通过反复取消引用接收器表达式的类型,将遇到的每种类型添加到列表中,最后尝试在末尾进行未调整大小的强制,如果成功则添加结果类型来获得这些。然后,对于每个候选 T,将 &T&mut T 添加到列表中,紧跟在 T 之后。

例如,如果接收者的类型为Box<[i32;2]>,那么候选类型将为Box<[i32;2]>&Box<[i32;2]>&mut Box<[i32;2]>[i32; 2](通过取消引用)、&[i32; 2]&mut [i32; 2]、@ 987654336@(通过无大小强制)、&[i32],最后是&mut [i32]

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

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

注意 [¹]:我实际上认为这个措辞是错误的。I've opened an issue。让我们忽略括号中的那句话。)


让我们详细浏览您的代码中的几个示例!对于你的例子,我们可以忽略“unsized coercion”和“inherent methods”的部分。

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

  • 创建候选接收器类型列表:
    • i32 不能被取消引用,所以我们已经完成了第 1 步。列表:[i32]
    • 接下来,我们添加&i32&mut i32。名单:[i32, &i32, &mut i32]
  • 搜索每个候选接收器类型的方法:
    • 我们找到<i32 as M>::m,它的接收者类型为i32。所以我们已经完成了。


到目前为止很容易。现在让我们选择一个更难的例子:(&&A).m()。接收者表达式的类型是&&A。我们执行以下步骤:

  • 创建候选接收器类型列表:
    • &&A 可以取消引用到 &A,因此我们将其添加到列表中。 &A 可以再次取消引用,因此我们还将A 添加到列表中。 A 不能被取消引用,所以我们停止。名单:[&&A, &A, A]
    • 接下来,对于列表中的每个类型T,我们在T 之后立即添加&T&mut T。名单:[&&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
  • 搜索每个候选接收器类型的方法:
    • 没有接收器类型&&A的方法,所以我们转到列表中的下一个类型。
    • 我们找到了方法<&&&A as M>::m,它确实具有接收器类型&&&A。这样我们就完成了。

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

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


  • A.m()<A as M>::m
    [⟪A⟫, &A, &mut A]
    
  • (&A).m()<A as M>::m
    [&A, &&A, &mut &A,
     ⟪A⟫, &A, &mut A]
    
  • (&&A).m()<&&&A as M>::m
    [&&A, ⟪&&&A⟫, &mut &&A,
     &A, &&A, &mut &A,
     A, &A, &mut A]
    
  • (&&&A).m()<&&&A as M>::m
    [⟪&&&A⟫, &&&&A, &mut &&&A,
     &&A, &&&A, &mut &&A,
     &A, &&A, &mut &A,
     A, &A, &mut A]
    
  • A.refm()<A as RefM>::refm
    [A, ⟪&A⟫, &mut A]
    
  • (&A).refm()<A as RefM>::refm
    [⟪&A⟫, &&A, &mut &A,
     A, &A, &mut A]
    
  • (&&A).refm()<A as RefM>::refm
    [&&A, &&&A, &mut &&A,
     ⟪&A⟫, &&A, &mut &A,
     A, &A, &mut A]
    
  • (&&&A).refm()<&&&A as RefM>::refm
    [&&&A, ⟪&&&&A⟫, &mut &&&A,
     &&A, &&&A, &mut &&A,
     &A, &&A, &mut &A,
     A, &A, &mut A]
    

【讨论】:

    【解决方案3】:

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

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

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

    关键是,在寻找使用哪个函数时,不要NOT使用调用“点运算符”的类型来确定使用哪个“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
    

    因此,当你编写代码时:

    (&X{val:42}).refm();

    功能

    fn refm(&X ) { println!("X::refm()");

    会被调用,因为参数类型是&X

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

    【讨论】:

      猜你喜欢
      • 2015-04-15
      • 2022-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多