Ich lerne / experimentiere mit Rust und in all der Eleganz, die ich in dieser Sprache finde, gibt es eine Besonderheit, die mich verblüfft und völlig fehl am Platz zu sein scheint.
Rust dereferenziert Zeiger automatisch, wenn Methodenaufrufe ausgeführt werden. Ich habe einige Tests durchgeführt, um das genaue Verhalten zu bestimmen:
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 == @
}
( Spielplatz )
Es scheint also mehr oder weniger:
- Der Compiler fügt so viele Dereferenzierungsoperatoren ein, wie zum Aufrufen einer Methode erforderlich sind.
- Der Compiler beim Auflösen von Methoden, die mit
&self
(Call-by-Reference) deklariert wurden :- Versuchen Sie zunächst, eine einzige Dereferenzierung von zu fordern
self
- Dann wird versucht, den genauen Typ von aufzurufen
self
- Versuchen Sie dann, so viele Dereferenzierungsoperatoren einzufügen, wie für eine Übereinstimmung erforderlich sind
- Versuchen Sie zunächst, eine einzige Dereferenzierung von zu fordern
- Methoden, die mit
self
(call-by-value) für den TypT
deklariert wurden, verhalten sich so, als ob sie mit&self
(call-by-reference) für den Typ deklariert worden wären&T
und als Referenz auf das aufgerufen würden, was sich auf der linken Seite des Punktoperators befindet. - Die oben genannten Regeln werden zuerst mit der eingebauten Roh-Dereferenzierung ausprobiert. Wenn keine Übereinstimmung vorliegt, wird die Überladung mit dem
Deref
Merkmal verwendet.
Was sind die genauen Regeln für die automatische Dereferenzierung? Kann jemand eine formale Begründung für eine solche Entwurfsentscheidung geben?