Rustの正確な自動逆参照ルールは何ですか?


181

私はRustで学習/実験しています。この言語で見つけた優雅さの中に、私を困惑させ、まったく場違いのように見える1つの特殊性があります。

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タイプに(call-by-value)を使用して宣言されたメソッドは、タイプに(call-by-reference)をT使用して宣言されたかのように動作します。&self&T、ドット演算子の左側にあるものへの参照で呼び出されたます。
  • 上記のルールは、生の組み込み逆参照で最初に試行され、一致しない場合Derefは、特性を持つオーバーロードが使用されます。

正確な自動逆参照ルールは何ですか?誰もがそのような設計決定について正式な根拠を与えることができますか?


良い答えが得られることを期待して、これをRustサブレディットにクロスポストしました!
シェプマスター、2015

さらに楽しくするには、ジェネリックで実験を繰り返し、結果を比較してみてください。
user2665887

回答:


137

あなたの疑似コードはかなり正しいです。この例では、foo.bar()whereを呼び出すメソッドがあるとしfoo: Tます。完全修飾構文(FQS)を使用して、メソッドがどのタイプで呼び出されているかを明確にします(例:A::bar(foo)または)A::bar(&***foo)。私はランダムな大文字の山を書くつもりです、それぞれTが常に元の変数の型を除いて、いくつかの任意の型/特性ですfooが、メソッドが呼び出されるです。

アルゴリズムの中核は次のとおりです。

  • 「逆参照ステップ」 U(つまり、設定U = TしてU = *Tから...)
    1. 方法があるかどうbarレシーバタイプ(タイプself方法では)と一致U正確には、(それを使用する「値法による」
    2. それ以外の場合は、1つの自動参照(テイク&または&mutレシーバー)を追加し、メソッドのレシーバーが一致する&U場合はそれを使用します(「autorefdメソッド」

特に、すべてがトレイトのタイプではなく、メソッドの「レシーバータイプ」を考慮しSelfます。つまり、メソッドを照合するときにimpl ... for Foo { fn method(&self) {} }考え&Foo、照合するときにfn method2(&mut self)考えます&mut Foo

内部ステップで有効なトレイトメソッドが複数ある場合はエラーになります(つまり、1または2のそれぞれで有効なトレイトメソッドは0または1つだけですが、それぞれに有効なトレイトメソッドは1つです。から1が最初に取得されます)、固有のメソッドが特性のメソッドよりも優先されます。一致するものが見つからずにループの最後に到達した場合もエラーになります。またDeref、ループを無限にする再帰的な実装があるとエラーになります(「再帰制限」に達します)。

これらのルールは、ほとんどの状況で何を意味しているように見えますが、明確なFQSフォームを書き込む機能は、一部のエッジケースや、マクロ生成コードの適切なエラーメッセージに非常に役立ちます。

自動参照が1つだけ追加されます。

  • すべての型が任意の数の参照を取得できるため、制限がない場合、状況は悪化/遅くなります。
  • 1つの参照&fooを取得するとfoo(それfoo自体のアドレスである)への強い接続が保持されますが、さらに取得を開始すると、それが失われ始めます。これ&&fooは、を格納するスタック上の一時変数のアドレスです&foo

次のタイプのfoo.refm()場合、呼び出しがあるとしますfoo

  • X、次にで始まるU = Xrefmレシーバータイプ&...があるため、ステップ1は一致せず、自動参照を取得する&Xとが得られ、これは(とSelf = X)と一致するため、呼び出しはRefM::refm(&foo)
  • &X、で始まりU = &X、これ&selfは最初のステップ(Self = X)で一致 するため、呼び出しはRefM::refm(foo)
  • &&&&&X、これはどちらのステップにも一致しません(トレイトは&&&&Xorに実装されていません&&&&&X)。したがって、一度参照を取得してを取得しますU = &&&&X。これは1と一致し(とSelf = &&&X)、呼び出しはRefM::refm(*foo)
  • Z、どちらのステップにも一致しないため、取得のためYに1回逆参照されますが、これも一致しません。したがって、逆参照され、取得のためXに1に一致しませんが、自動参照後に一致しますRefM::refm(&**foo)。したがって、呼び出しはです。
  • &&A、1は一致せず、2も一致しません。トレイトは&A(1の場合)または&&A(2の場合)に実装されていないため、1 &Aに一致するに逆参照されます。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する必要がfooあるため、から移動する必要があるため、エラーになります。
  • &&A、1。は一致しませんが、自動参照は&&&Aと一致M::m(&foo)Self = &&&Aます。したがって、呼び出しはで行われます。

(この回答はコードに基づいており、(少し古い)READMEにかなり近いです。コンパイラ/言語のこの部分の主な著者であるNiko Matsakisもこの回答をちらりと見ました。)


15
この回答は網羅的で詳細に思われますが、短くてアクセス可能なルールの要約が欠けていると思います。このような概要の 1つがShepmasterによるこのコメントで示されています。「[ デリファレンスアルゴリズム]は可能な限り何度もderefし(&&String-> &String-> String-> str)、最大で一度参照(str-> &str)します。
Lii 2017

(私がその説明がどれほど正確で完全かはわかりません。)
Lii

1
自動逆参照はどのような場合に発生しますか?メソッド呼び出しのレシーバー式にのみ使用されますか?フィールドへのアクセスについても?割り当ての右側?左側?関数パラメーター?戻り値の式?
Lii

1
注:現在、nomiconにはこの回答から情報を盗み、static.rust-lang.org
doc /

1
強制は(A)この前に試行されたか、または(B)この後に試行されたか、または(C)このアルゴリズムのすべてのステップで試行されたか、または(D)他の何かですか?
haslersn 2018

8

Rustリファレンスには、メソッド呼び出し式に関する章があります。以下の最も重要な部分をコピーしました。注意:以下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]、([i32]無サイズ強制することによって)&[i32]、最終的に、そして&mut [i32]

次に、候補タイプごとTに、次の場所でそのタイプのレシーバーを使用して可視メソッドを検索します。

  1. Tの固有のメソッド(T[¹]に直接実装されるメソッド)。
  2. によって実装された目に見える特性によって提供されるメソッドのいずれかT。[...]

[¹]に関する注意:私は実際、この言い回しは間違っていると思います。問題をオープンしました。括弧内のその文を無視してみましょう。)


コードからいくつかの例を詳しく見てみましょう!例として、「サイズ変更されていない強制」および「固有のメソッド」に関する部分は無視できます。

(*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、の&mut T直後にを追加しTます。リスト:[&&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
  • 候補となる各レシーバータイプのメソッドの検索:
    • レシーバータイプにはメソッドがない&&Aため、リストの次のタイプに進みます。
    • <&&&A as M>::m実際にレシーバー・タイプを持つメソッドを見つけます&&&A。これで完了です。

以下は、すべての例の候補受信者リストです。囲まれているタイプ⟪x⟫は、「勝った」タイプ、つまり、フィッティング方法が見つかった最初のタイプです。また、リストの最初のタイプは常にレシーバー式のタイプであることに注意してください。最後に、リストを3行でフォーマットしましたが、それは単なるフォーマットです。このリストはフラットリストです。

  • (*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]
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.