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

所以,似乎或多或少:

  • 编译器将插入调用方法所需的尽可能多的解引用运算符.
  • The compiler, when resolving methods declared using &self (call-by-reference):
    • 第一次try 取消self次引用
    • 然后try 调用self的确切类型
    • 然后,try 插入匹配所需的尽可能多的解引用运算符
  • T类型使用self(按值调用)声明的方法的行为就像对&T类型使用&self(按引用调用)声明的方法一样,并在引用点运算符左侧的任何对象时调用它们.
  • 上述规则首先在原始的内置解引用中try ,如果没有匹配,则使用Deref trait重载.

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

推荐答案

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

算法的核心是:

值得注意的是,所有因素都考虑了方法的"接受者类型",notSelf类型的trait ,即impl ... for Foo { fn method(&self) {} }在匹配方法时考虑&Foofn method2(&mut self)在匹配时考虑&mut Foo.

如果在内部步骤中存在多个有效的特征方法(即,在1.或2.中的每个步骤中只能有零个或一个有效的特征方法,但每个步骤都可以有一个有效的特征方法:1中的一个将首先被采用),则这是一个错误,并且固有方法优先于特征方法.如果我们在循环结束时没有找到任何匹配项,这也是一个错误.使用递归Deref实现也是一个错误,这使得循环无限(它们将达到"递归极限").

在大多数情况下,这些规则似乎起到了我所说的作用,尽管在某些边缘情况下,能够编写明确的FQS表单非常有用,对于宏生成的代码来说,这些规则也非常有用.

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

  • 如果没有界限,事情就会变得糟糕/缓慢,因为每种类型都可以有任意数量的引用
  • 取一个引用&foo会保留与foo的强连接(它是foo本身的地址),但取更多引用则会丢失它:&&foo是堆栈上存储&foo的某个临时变量的地址.

例子

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

  • X,然后我们从U = X开始,refm的接收器类型是&...,所以第一步不匹配,自动参考得到&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也不匹配.对于特征1到&A,哪一个是取消引用的.,Self = A

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

  • A,然后U = A直接匹配self,所以通话是M::m(foo)Self = A
  • &A,然后是1.不匹配,2也不匹配.(&A&&A都没有实现该特征),因此它被解引用到AA确实匹配,但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也浏览了这个答案.)

Rust相关问答推荐

Rust为什么应用于引用的操作符可以强制,而具有显式类型的let则不能?

为什么是!为Rust中的RwLockReadGuard和RwLockWriteGuard实现的发送特征?

PyReadonlyArray2到Vec T<>

将大小为零的类型实例存储到空指针中

如果LET;使用布尔表达式链接(&Q);

在不重写/专门化整个函数的情况下添加单个匹配手臂到特征的方法?

RUST应用程序正在退出,错误代码为:(退出代码:0xc0000005,STATUS_ACCESS_VIOLATION)

通过RabbitMQ取消铁 rust 中长时间运行的人造丝任务的策略

如何使用Actix Web for Rust高效地为大文件服务

习语选项<;T>;到选项<;U>;当T->;U用From定义

如何从宏调用闭包?

仅发布工作区的二进制 crate

tokio::spawn 有和没有异步块

‘&T as *const T as *mut T’ 在 ‘static mut’ 项目中合适吗?

注释闭包参数强调使用高阶排定特征界限

Rust中的标记特征是什么?

&self 参数在 trait 的功能中是必需的吗?

在运行时在 Rust 中加载字体

使用方法、关联函数和自由函数在 Rust 中初始化函数指针之间的区别

如何在 Rust 中编写涉及异步的重试函数