gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-29 07:48:56 24 4
gpt4 key购买 nike

我正在学习/试验 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 声明的一样(call-by-reference) 类型 &T并调用对点运算符左侧的任何内容的引用。
  • 上面的规则首先尝试使用原始的内置解引用,如果没有匹配,则重载 Deref特性被使用。

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

    最佳答案

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

  • 每个"dereference step" U (即先设置 U = T 然后设置 U = *T ,...)
  • 如果有方法 bar其中接收器类型(方法中 self 的类型)匹配 U确切地说,使用它(a "by value method")
  • 否则,添加一个自动引用(取接收器的 &&mut),并且,如果某个方法的接收器匹配 &U , 使用它 ( an "autorefd method" )


  • 值得注意的是,一切都考虑了方法的“接收器类型”,而不是 Self特征的类型,即 impl ... for Foo { fn method(&self) {} }想想 &Foo匹配方法时,和 fn method2(&mut self)会考虑 &mut Foo匹配时。
    如果在内部步骤中有多个有效的 trait 方法(也就是说,在 1. 或 2. 中的每一个中只能有零个或一个有效的 trait 方法,但可以有一个对每个有效的 trait 方法:一个from 1 将首先被采用),并且固有方法优先于特征方法。如果我们在没有找到任何匹配的情况下到达循环的末尾,这也是一个错误。有递归 Deref也是错误的实现,这使循环无限(它们将达到“递归限制”)。
    在大多数情况下,这些规则似乎是我的意思,尽管能够编写明确的 FQS 表单在某些边缘情况下非常有用,并且对于宏生成的代码的合理错误消息非常有用。
    只添加了一个自动引用,因为
  • 如果没有限制,事情就会变坏/变慢,因为每种类型都可以有任意数量的引用
  • 取一引用&foofoo 保持密切联系(它是 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,但在自动引用后匹配,所以调用是 RefM::refm(&**foo) .
  • &&A , 1. 不匹配, 2. 也不匹配,因为该 trait 没有为 &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 都没有实现特征),所以它被取消引用到 A , 确实匹配,但 M::m(*foo)需要服用 A按值(value),因此移出 foo ,因此错误。
  • &&A , 1. 不匹配,但自动引用给出 &&&A ,它确实匹配,所以调用是 M::m(&foo)Self = &&&A .

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

    关于rust - Rust 的确切自动解引用规则是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36426207/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com