J'apprends / j'expérimente avec Rust, et dans toute l'élégance que je trouve dans cette langue, il y a une particularité qui me déroute et qui semble totalement déplacée.
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 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 == @
}
( Aire de jeux )
Donc, il semble que, plus ou moins:
- Le compilateur insérera autant d'opérateurs de déréférencement que nécessaire pour appeler une méthode.
- Le compilateur, lors de la résolution des méthodes déclarées à l'aide de
&self
(appel par référence):- Essaie d'abord d'appeler à une seule déréférence de
self
- Essaie ensuite d'appeler le type exact de
self
- Ensuite, essaie d'insérer autant d'opérateurs de déréférencement que nécessaire pour une correspondance
- Essaie d'abord d'appeler à une seule déréférence de
- Les méthodes déclarées en utilisant
self
(appel par valeur) pour typeT
se comportent comme si elles étaient déclarées en utilisant&self
(appel par référence) pour type&T
et appelées sur la référence à ce qui se trouve sur le côté gauche de l'opérateur point. - Les règles ci-dessus sont d'abord essayées avec un déréférencement intégré brut, et s'il n'y a pas de correspondance, la surcharge avec
Deref
trait est utilisée.
Quelles sont les règles exactes de déréférencement automatique? Quelqu'un peut-il expliquer formellement une telle décision de conception?
Réponses:
Votre pseudo-code est à peu près correct. Pour cet exemple, supposons que nous ayons un appel de méthode
foo.bar()
wherefoo: T
. Je vais utiliser la syntaxe pleinement qualifiée (FQS) pour être sans ambiguïté sur le type avec lequel la méthode est appelée, par exempleA::bar(foo)
ouA::bar(&***foo)
. Je vais juste écrire une pile de lettres majuscules aléatoires, chacune étant juste un type / trait arbitraire, sauf qu'ilT
s'agit toujours du type de la variable d'origine surfoo
laquelle la méthode est appelée.Le cœur de l'algorithme est:
U
(c'est-à-dire définirU = T
et ensuiteU = *T
, ...)bar
où le type de récepteur (le type deself
dans la méthode) correspondU
exactement, utilisez-la ( une "méthode par valeur" )&
ou&mut
du receveur), et, si le receveur d'une méthode correspond&U
, utilisez-la ( une "méthode autorefd" )Notamment, tout considère le "type de récepteur" de la méthode, pas le
Self
type du trait, c'est-à-dire qu'ilimpl ... for Foo { fn method(&self) {} }
réfléchit&Foo
lors de l'appariement de la méthode, et auquelfn method2(&mut self)
il réfléchirait&mut Foo
lors de l'appariement.C'est une erreur s'il y a jamais 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 méthodes 1. ou 2., mais il peut y en avoir une valide pour chacune: celle à partir de 1 sera prise en premier), et les méthodes inhérentes ont la priorité sur les caractéristiques. C'est aussi une erreur si nous arrivons à la fin de la boucle sans rien trouver qui corresponde. C'est aussi une erreur d'avoir des
Deref
implémentations 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 circonstances, bien que la capacité d'écrire le formulaire FQS sans ambiguïté soit très utile dans certains cas extrêmes, et 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 forte connexion avecfoo
(c'est l'adresse d'foo
elle - même), mais en prendre plus commence à la perdre:&&foo
c'est l'adresse d'une variable temporaire sur la pile qui stocke&foo
.Exemples
Supposons que nous ayons un appel
foo.refm()
, sifoo
a le type:X
, alors nous commençons parU = X
,refm
a le type de récepteur&...
, donc l'étape 1 ne correspond pas, prendre une auto-ref nous donne&X
, et cela correspond (avecSelf = X
), donc l'appel estRefM::refm(&foo)
&X
, commence parU = &X
, qui correspond&self
à la première étape (avecSelf = X
), et donc l'appel estRefM::refm(foo)
&&&&&X
, cela ne correspond à aucune étape (le trait n'est pas implémenté pour&&&&X
ou&&&&&X
), donc nous déréférencer une fois pour obtenirU = &&&&X
, qui correspond à 1 (avecSelf = &&&X
) et l'appel estRefM::refm(*foo)
Z
, ne correspond à aucune des étapes, il est donc déréférencé une fois, pour obtenirY
, ce qui ne correspond pas non plus, donc il est à nouveau déréférencé, pour obtenirX
, qui ne correspond pas à 1, mais correspond après l'autorefing, donc l'appel estRefM::refm(&**foo)
.&&A
, le 1. ne correspond pas et ne le fait pas non plus 2. puisque le trait n'est pas implémenté pour&A
(pour 1) ou&&A
(pour 2), il est donc déréférencé&A
, ce qui correspond à 1., avecSelf = A
Supposons que nous ayons
foo.m()
, et ceA
n'est pas leCopy
cas, sifoo
a le type:A
, puisU = A
correspondself
directement pour que l'appel soitM::m(foo)
avecSelf = A
&A
, alors 1. ne correspond pas, ni 2. (ni&A
n'implémente&&A
le trait), donc il est déréférencé àA
, ce qui correspond, maisM::m(*foo)
nécessite de prendreA
par valeur et donc de sortirfoo
, d'où l'erreur.&&A
, 1. ne correspond pas, mais l'autorefing donne&&&A
, ce qui correspond, donc l'appel estM::m(&foo)
avecSelf = &&&A
.(Cette réponse est basée sur le code et est raisonnablement proche du (légèrement obsolète) README . Niko Matsakis, l'auteur principal de cette partie du compilateur / langage, a également jeté un coup d'œil sur cette réponse.)
la source
&&String
->&String
->String
->str
) puis référencé au maximum une fois (str
->&str
)".La référence Rust contient un chapitre sur l'expression d'appel de méthode . J'ai copié la partie la plus importante ci-dessous. Rappel: nous parlons d'une expression
recv.m()
, oùrecv
est appelée "expression du récepteur" ci-dessous.( Remarque à propos de [¹] : je pense en fait que cette formulation est erronée. J'ai ouvert un problème . Ignorons simplement cette phrase entre parenthèses.)
Passons en revue quelques exemples de votre code en détail! Pour vos exemples, nous pouvons ignorer la partie sur la "coercition non dimensionnée" et les "méthodes inhérentes".
(*X{val:42}).m()
: le type de l'expression du récepteur esti32
. Nous effectuons ces étapes:i32
ne peut pas être déréférencé, nous en avons donc déjà terminé avec l'étape 1. Liste:[i32]
&i32
et&mut i32
. Liste:[i32, &i32, &mut i32]
<i32 as M>::m
qui a le type de récepteuri32
. Nous avons donc déjà terminé.Jusqu'ici si facile. Prenons maintenant un exemple plus difficile:
(&&A).m()
. Le type de l'expression du récepteur est&&A
. Nous effectuons ces étapes:&&A
peut être déréférencé&A
, donc nous ajoutons cela à la liste.&A
peut être à nouveau déréférencé, nous ajoutons donc égalementA
à la liste.A
ne peut pas être déréférencé, alors nous nous arrêtons. Liste:[&&A, &A, A]
T
de la liste, nous ajoutons&T
et&mut T
immédiatement aprèsT
. Liste:[&&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
&&A
, nous passons donc au type suivant dans la liste.<&&&A as M>::m
qui a en effet le type de récepteur&&&A
. Nous avons donc terminé.Voici les listes de destinataires candidats pour tous vos exemples. Le type inclus dans
⟪x⟫
est celui qui a "gagné", c'est-à-dire le premier type pour lequel une méthode d'ajustement a pu être trouvée. Souvenez-vous également que le premier type de la liste est toujours le type de l'expression du récepteur. Enfin, j'ai formaté la liste en lignes de trois, mais ce n'est que du formatage: cette liste est une liste plate.(*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]
la source