小能豆

Rust 的确切自动解除引用规则是什么?

rust

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

游乐场

所以,似乎或多或少:

  • 编译器将根据需要插入尽可能多的解引用运算符来调用方法。

  • 编译器在解析使用

&self

(按引用调用)声明的方法时:

  • 首先尝试调用单个取消引用self
  • 然后尝试调用确切的类型self
  • 然后,尝试插入匹配所需的尽可能多的取消引用运算符

  • self使用(按值调用)类型声明的方法的T行为就好像它们是使用&self(按引用调用)类型声明&T的,并在对点运算符左侧的任何内容的引用上调用。

  • 首先使用原始内置解引用尝试上述规则,如果没有匹配,则Deref使用特征重载。

确切的自动取消引用规则是什么?谁能为这样的设计决策给出任何正式的理由?


阅读 93

收藏
2024-05-21

共1个答案

小能豆

Rust中的自动解引用规则确实是一种设计选择,旨在使代码更具表达力和简洁性。通过自动解引用,您可以像操作原始类型一样操作引用类型,而不必手动取消引用。这种机制允许您编写更加紧凑和易读的代码,因为不需要显式地取消引用。

以下是自动解引用的一些规则:

  1. 当调用方法或访问字段时,如果目标类型与实际类型不匹配,则编译器会尝试自动取消引用,直到找到匹配的方法或字段。

  2. 当解引用引用类型时,编译器会自动应用 Deref trait 来查找匹配的方法或字段。

  3. 编译器首先尝试通过对 self 执行一系列解引用操作来调用方法。如果找到匹配的方法,则编译器会自动应用所需的解引用操作。

  4. 如果找不到匹配的方法,则编译器会尝试通过应用 Deref trait 来解引用 self,直到找到匹配的方法。

  5. 自动解引用规则还适用于方法调用的左侧,以及通过点运算符访问的字段和方法。

总的来说,自动解引用规则使得在 Rust 中操作引用类型和原始类型变得更加方便和直观,同时保持了代码的清晰性和可读性。这种设计决策有助于提高代码的表达力和简洁性,并且与 Rust 的所有权和借用模型完美契合。

2024-05-21