J'apprends/expérimente avec Rust, et dans toute l'élégance que je trouve dans cette langue, il y a une particularité qui me déconcerte et semble totalement hors de propos.
Rust déréférence automatiquement les pointeurs lors des appels de méthode. J'ai fait quelques tests pour déterminer le comportement exact:
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 == @
}
Donc, il semble que, plus ou moins:
&self
(appel par référence): self
self
self
(appel par valeur) pour le type T
se comportent comme si elles avaient été déclarées à l'aide de &self
(appel par référence) pour le type &T
et appelle la référence à tout ce qui se trouve à gauche de l’opérateur de point.Deref
trait est utilisée.Quelles sont les règles exactes de déréférencement automatique? Quelqu'un peut-il donner une justification formelle à une telle décision de conception?
Votre pseudo-code est à peu près correct. Pour cet exemple, supposons que nous ayons un appel de méthode foo.bar()
où foo: T
. Je vais utiliser le syntaxe complète (FQS) pour indiquer sans ambiguïté le type de la méthode appelée, par exemple. A::bar(foo)
ou A::bar(&***foo)
. Je vais juste écrire une pile de lettres majuscules aléatoires, chacune correspondant à un type/trait quelconque, sauf que T
est toujours le type de la variable d'origine foo
de la méthode. sur.
Le noyau de l'algorithme est:
U
(c'est-à-dire, définissez U = T
Puis U = *T
, ...) bar
où le type de destinataire (le type de self
dans la méthode) correspond exactement à U
, utilisez-le ( ne méthode "par valeur") )&
ou &mut
du destinataire) et, si le destinataire de la méthode correspond à &U
, utilisez-le ( n " méthode autorefd " )Notamment, tout considère le "type de destinataire" de la méthode, et non le type Self
du trait, c'est-à-dire impl ... for Foo { fn method(&self) {} }
pense à &Foo
lors de l'appariement de la méthode, et fn method2(&mut self)
pense à &mut Foo
à l'appariement.
C’est une erreur s’il ya plusieurs méthodes de trait valides dans les étapes internes (c’est-à-dire qu’il ne peut y avoir que zéro ou une méthode de trait valide dans chacune des étapes 1. ou 2., mais qu’il en existe une valide pour chacune: la à partir de 1 sera pris en premier), et les méthodes inhérentes priment sur celles de trait. C'est également une erreur si nous arrivons à la fin de la boucle sans rien trouver qui corresponde. C'est aussi une erreur d'avoir des implémentations Deref
récursives, qui rendent la boucle infinie (elles atteindront la "limite de récursivité").
Ces règles semblent faire ce que je veux dire dans la plupart des cas, bien qu'il soit très utile d’écrire le formulaire FQS sans ambiguïté est très utile dans certains cas Edge, ainsi que pour les messages d’erreur sensibles pour le code généré par macro.
Une seule référence automatique est ajoutée car
&foo
conserve une connexion forte à foo
(c'est l'adresse de foo
lui-même), mais en prenant plus commence à la perdre: &&foo
est l'adresse d'une variable temporaire sur la pile qui stocke &foo
.Supposons que nous ayons un appel foo.refm()
, si foo
a le type:
X
, nous commençons par U = X
, refm
a le type de destinataire &...
, donc l'étape 1 ne correspond pas, prendre un auto-ref nous donne &X
, Et cela correspond (avec Self = X
), Donc l'appel est RefM::refm(&foo)
&X
Commence par U = &X
, Ce qui correspond à &self
À la première étape (avec Self = X
), Et l'appel est donc RefM::refm(foo)
&&&&&X
, Cela ne correspond à aucune étape (le trait n'est pas implémenté pour &&&&X
Ou &&&&&X
), Donc nous déréférencons une fois pour obtenir U = &&&&X
, qui correspond à 1 (avec Self = &&&X
) et l'appel est RefM::refm(*foo)
Z
, ne correspond à aucune étape, il est donc déréférencé une fois pour obtenir Y
, ce qui ne correspond pas non plus. Il est donc déréférencé à nouveau pour obtenir X
. ne correspond pas à 1, mais après la lecture automatique, l'appel est donc RefM::refm(&**foo)
.&&A
, Le 1. ne correspond pas et 2. non plus, car le trait n'est pas implémenté pour &A
(Pour 1) ou &&A
(Pour 2), donc est déréférencé à &A
, ce qui correspond à 1., avec Self = A
Supposons que nous ayons foo.m()
, et que A
ne soit pas Copy
, si foo
a le type:
A
, alors U = A
correspond à self
directement, donc l'appel est M::m(foo)
avec Self = A
&A
, Alors 1. ne correspond pas, ni 2. (ni &A
Ni &&A
Ne mettent en oeuvre le trait), de sorte qu'il est déréférencé à A
, qui correspond, mais M::m(*foo)
nécessite de prendre A
par valeur et donc de sortir de foo
, d'où l'erreur.&&A
, 1. ne correspond pas, mais la lecture automatique donne &&&A
, Ce qui correspond. L'appel est donc M::m(&foo)
avec Self = &&&A
.(Cette réponse est basée sur le code , et est raisonnablement proche du fichier README (légèrement dépassé) . Niko Matsakis, l'auteur principal de cette partie du compilateur/langage, jeté un coup d’œil sur cette réponse.)