[解決済み】Rustの正確な自動再参照のルールは?
質問
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
(コール・バイ・バリュー) タイプのT
を使用して宣言されたかのように振る舞います。&self
(参照渡し)で、型&T
で、ドット演算子の左側にあるものへの参照で呼び出されます。 -
上記のルールは、まず生のビルトインデリフェレンシングで試され、マッチしない場合、オーバーロードで
Deref
の形質が使われます。
正確な自動再参照のルールは?また、そのような設計上の決定に対して、正式な根拠を示すことができる人はいますか?
解決方法は?
あなたの擬似コードはかなり正しいです。この例では、メソッド呼び出しがあったとします。
foo.bar()
ここで
foo: T
. を使うことにします。
完全修飾構文
(FQS) を使用すると、メソッドがどのような型で呼び出されているのかが明確になります。
A::bar(foo)
または
A::bar(&***foo)
. ランダムに大文字を書き連ねるだけで、それぞれは任意の型/特性ですが、ただし
T
は常に元の変数の型
foo
で、そのメソッドが呼び出される。
アルゴリズムの核となるのは
-
各
参照ステップ"
U
(を設定します(つまりU = T
を指定し、次にU = *T
, ...)-
メソッドがある場合
bar
ここで、レシーバの型(self
はメソッド内のU
を使用します。 a "値による方法"。 ) -
そうでない場合は、自動参照を1つ追加します。
&
または&mut
と一致する場合、そのメソッドのレシーバは&U
であれば、それを使う ( autorefd method" )
-
メソッドがある場合
注目すべきは、すべてがメソッドの "receiver type" を考慮していることです。
ではなく
は
Self
の型、すなわち、特質が
impl ... for Foo { fn method(&self) {} }
を考える
&Foo
メソッドとマッチングするときに
fn method2(&mut self)
を考えるだろう。
&mut Foo
をマッチングさせる。
内側のステップで有効な形質メソッドが複数ある場合(つまり、1.または2.のそれぞれで有効な形質メソッドはゼロか1つだけで、それぞれで有効なものがある可能性があります:1からのものが最初に取られます)、エラーとなり、固有のメソッドは形質メソッドよりも優先されます。また、ループの最後まで行って、マッチするものが見つからなかった場合もエラーとなります。また、再帰的な
Deref
の実装では、ループが無限大になってしまいます("recursion limit"にヒットしてしまいます)。
曖昧さのないFQS形式を書く能力は、いくつかのエッジケースや、マクロで生成されたコードの賢明なエラーメッセージのために非常に便利ですが、これらのルールは、ほとんどの状況で何を意味するかというと、そうです。
自動参照は1つだけ追加されます。
- すべての型は任意の数の参照を持つことができるので、束縛がなかった場合、物事は悪く/遅くなる
-
1つのリファレンスを取る
&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にはマッチしませんが、オートリフィングの結果マッチするので、呼び出しは次のようになります。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
を実装している)ため、デリファレンスはA
というのは一致するのですがM::m(*foo)
を取る必要があります。A
を値で指定し、その結果foo
そのため、エラーが発生します。 -
&&A
は一致しませんが、オートリファイリングにより&&&A
であり、これは一致するので、呼び出しはM::m(&foo)
とSelf = &&&A
.
(この回答は コード であり、かつ は、(少し古いですが)READMEにそれなりに近いです。 . この部分のメイン作者であるNiko Matsakis氏もこの回答に目を通しました)。
関連
-
[解決済み] Rustで絶対値を求めるには?
-
[解決済み] 構造体のフィールドをメソッドから変異させるには?
-
[解決済み] Rustで絶対値を求めるには?
-
[解決済み] Rust の `String` と `str` の違いは何ですか?
-
[解決済み] Rustのユニットテストでprintln! が動作しないのはなぜ?
-
[解決済み】文字列をint型に変換する?
-
[解決済み] 同じプロジェクトの別のファイルからモジュールをインクルードする方法は?
-
[解決済み] このクエスチョンマークの演算子は何についてですか?
-
[解決済み] Cargoで複数のバイナリをビルドするにはどうしたらいいですか?
-
[解決済み] ローカルの未公開クレートを使うには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] rustupでインストールしたRustをアンインストールするには?
-
[解決済み] RustのRc::clone(&rc)とrc.clone()は何か違いがあるのでしょうか?また、それによるコンパイルの最適化はあるのでしょうか?
-
[解決済み] Rustで絶対値を求めるには?
-
[解決済み] 構造体や配列を印刷するには?
-
[解決済み] なぜRustコンパイラは、2つのミュータブル参照がエイリアスできないと仮定してコードを最適化しないのですか?
-
[解決済み】文字列をint型に変換する?
-
[解決済み】ライブラリとバイナリの両方を持つRustパッケージ?
-
[解決済み] CopyとCloneの違いは何ですか?
-
[解決済み] バイトのベクター(u8)を文字列に変換するには?
-
[解決済み] Cargoで複数のバイナリをビルドするにはどうしたらいいですか?