Was sind Rost - genaue auto-Dereferenzierung Regeln?
Ich bin lernen/Experimentieren mit Rost, und in all der Eleganz, die ich finde, in dieser Sprache, es ist eine Besonderheit, dass verwirrt mich und erscheint völlig deplatziert.
Rost automatisch dereferenziert Zeiger, wenn man Methodenaufrufe. Ich machte einige tests, um zu bestimmen, das genaue Verhalten:
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<'a> M for &'a X { fn m(self) { println!("&X::m()"); } }
impl<'a, 'b> M for &'a &'b X { fn m(self) { println!("&&X::m()"); } }
impl<'a, 'b, 'c> M for &'a &'b &'c 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<'a> RefM for &'a X { fn refm(&self) { println!("&X::refm()"); } }
impl<'a, 'b> RefM for &'a &'b X { fn refm(&self) { println!("&&X::refm()"); } }
impl<'a, 'b, 'c> RefM for &'a &'b &'c 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 }
}
struct A;
impl std::marker::Copy for A {}
impl M for A { fn m(self) { println!("A::m()"); } }
impl<'a, 'b, 'c> M for &'a &'b &'c A { fn m(self) { println!("&&&A::m()"); } }
impl RefM for A { fn refm(&self) { println!("A::refm()"); } }
impl<'a, 'b, 'c> RefM for &'a &'b &'c 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::refm() , 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 == **@
(*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 == **@
Y{val:42}.refm(); //i32::refm() , self == *@
Z{val:Y{val:42}}.refm(); //i32::refm() , self == **@
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 == @
}
So, es scheint, dass mehr oder weniger:
- Der compiler fügt so viele dereferenzieren Betreiber wie notwendig, um eine Methode aufzurufen.
- Der compiler beim auflösen von Methoden deklariert, mit
&self
(call-by-reference):- Ersten mal versucht, den Aufruf für eine einzelne dereferenzieren von
self
- Dann versucht der Aufruf für die genaue Art der
self
- Dann versucht Sie das einfügen so viele dereferenzieren Betreiber als notwendig für ein Spiel
- Ersten mal versucht, den Aufruf für eine einzelne dereferenzieren von
- Methoden deklariert, mit
self
(call-by-value) für TypT
Verhalten, als wenn Sie deklariert wurden, mit&self
(call-by-reference) für Typ&T
genannt und der Verweis auf das, was ist auf der linken Seite des Punkt-operators. - Die oben genannten Regeln sind zuerst versucht, mit raw-built-in-array-Dereferenzierung, und wenn es keine übereinstimmung, wird die überlast mit
Deref
Merkmal verwendet wird.
Was sind die genauen automatische Dereferenzierung Regeln? Kann jemand geben eine formale Begründung für eine solche design-Entscheidung?
Ich habe cross-gepostet, dies zu Rost subreddit in der Hoffnung ein paar gute Antworten!
Für zusätzlichen Spaß, versuchen Sie, wiederholen Sie das experiment im Bereich der Generika und die Ergebnisse vergleichen.
Für zusätzlichen Spaß, versuchen Sie, wiederholen Sie das experiment im Bereich der Generika und die Ergebnisse vergleichen.
InformationsquelleAutor kFYatek | 2015-02-14
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ihre pseudo-code ist ziemlich viel richtig. Für dieses Beispiel nehmen wir an, wir hatten einen Aufruf der Methode
foo.bar()
wofoo: T
. Ich werde der voll qualifizierte syntax (FQS) eindeutig sein, welche Art der Methode, die aufgerufen wird, z.B.A::bar(foo)
oderA::bar(&***foo)
. Ich bin gerade dabei zu schreiben, ein Haufen von zufälligen GROSSBUCHSTABEN, die jeweils nur einige beliebige Typ/Merkmal, außerT
ist immer der Typ der ursprünglichen variablefoo
dass die Methode aufgerufen wird.Den Kern des Algorithmus ist:
U
(setU = T
und dannU = *T
, ...)bar
wo die receiver-Typ (der Typ vonself
in der Methode) entsprichtU
genau , esein "by-value-Methode")&
oder&mut
des Empfängers), und, falls eine Methode ist der receiver entspricht&U
verwenden Sie es (ein "autorefd-Methode")Allem, alles hält den "Empfänger-Typ" der Methode, nicht die
Self
Typ der Eigenschaft, d.h.impl ... for Foo { fn method(&self) {} }
denkt&Foo
bei der Anpassung der Methode, undfn method2(&mut self)
würde denken&mut Foo
beim matching.Ist es ein Fehler, wenn es überhaupt mehrere trait-Methoden gültig in die inneren Schritte (das heißt, es kann nur null sein, oder eine trait-Methoden gilt in der jeweils 1. oder 2., aber es kann sein, eine gültige für jeden: der eine, von 1 werden zuerst genommen), und inhärenten Methoden Vorrang vor der Eigenschaft lieben. Es ist auch ein Fehler, wenn wir das Ende der Schleife, ohne etwas zu finden, der passt. Es ist auch ein Fehler zu haben rekursive
Deref
- Implementierungen, die die Schleife unendlich (Sie werden auf die "rekursions-Grenze").Diese Regeln scheinen zu tun-was-ich-meine in den meisten Fällen, obwohl die Fähigkeit zu schreiben, die eindeutig FQS form ist sehr nützlich in einige Grenzfälle, die für eine sinnvolle Fehlermeldungen für makro-code generiert.
Nur ein auto-Referenz Hinzugefügt wird, weil
&foo
behält eine starke Verbindung zufoo
(es ist die Adresse vonfoo
selbst), aber nehmen mehr startet, es zu verlieren:&&foo
ist die Adresse, von einigen temporären Variablen auf dem stack, speichert&foo
.Beispiele
Nehmen wir an, wir haben einen Anruf
foo.refm()
, wennfoo
Typ:X
, dann beginnen wir mitU = X
,refm
hat receiver-Typ&...
, also Schritt 1 nicht entsprechen, nehmen Sie ein auto-ref gibt uns&X
, und diese passt (mitSelf = X
), also der Aufruf istRefM::refm(&foo)
&X
beginnt mitU = &X
, was mit&self
im ersten Schritt (mitSelf = X
), und so wird der AnrufRefM::refm(foo)
&&&&&X
, das passt nicht entweder von Schritt (die Eigenschaft ist nicht implementiert, für&&&&X
oder&&&&&X
), so dass wir dereferenzieren einmal zuU = &&&&X
, die Spiele 1 (mitSelf = &&&X
) und der Anruf wirdRefM::refm(*foo)
Z
, nicht mit dem übereinstimmt, entweder Schritt, so ist es dereferenziert einmal, umY
, die auch nicht übereinstimmen, so ist es dereferenziert wieder, umX
, die nicht mit 1, sondern entspricht nach autorefing, so wird der AnrufRefM::refm(&**foo)
.&&A
1. nicht mit und auch nicht 2. da die Eigenschaft nicht implementiert&A
(für 1) oder&&A
(für 2), so ist es aufgelöst zu&A
, was mit 1., mitSelf = A
Nehmen wir an, wir haben
foo.m()
, und dassA
ist nichtCopy
, wennfoo
Typ:A
, dannU = A
entsprichtself
direkt an, so wird der AnrufM::m(foo)
mitSelf = A
&A
, dann 1. passt nicht, und auch nicht 2. (weder&A
noch&&A
implementieren Sie die Eigenschaft), so ist es aufgelöst zuA
, die nicht übereinstimmen, aberM::m(*foo)
erfordert die EinnahmeA
von Wert und damit der Auszug aus derfoo
, daher der Fehler.&&A
, 1. passt nicht, aber autorefing gibt&&&A
, die nicht übereinstimmen, so wird der AnrufM::m(&foo)
mitSelf = &&&A
.(Diese Antwort basiert auf der code, und ist ziemlich nahe an der (etwas veraltete) README. Niko Matsakis, der Haupt-Autor dieses Teils der compiler/Sprache, linste über diese Antwort.)
&&String
->&String
->String
->str
) und dann Verweis auf max einmal (str
->&str
)".(Ich weiß nicht, wie genau und vollständig, die Erklärung ist mir.)
In welchem Fall müssen automatische Dereferenzierung auftreten? Ist es nur für den Empfänger Ausdruck für die Methode aufrufen? Für Feld-Zugriffe auch? Zuweisung die Rechte Seite? Linken Seiten? Funktion Parameter? Return-Wert Ausdrücken?
Hinweis: Derzeit ist das nomicon eine TODO-Hinweis auf Diebstahl von Informationen aus dieser Antwort und schreiben Sie es in static.rust-lang.org/doc/master/nomicon/dot-operator.html
Ist Zwang (A) versucht, bevor Sie diese oder (B) versucht, nach dieser oder (C) versucht, in jedem Schritt dieses Algorithmus oder (D) etwas anderes?
InformationsquelleAutor huon