web-dev-qa-db-fra.com

Comment Rust implémente la réflexion?

Rust a le trait Any, mais il a aussi une politique "ne payez pas pour ce que vous n'utilisez pas". Comment Rust implémente la réflexion?

Je suppose que Rust utilise le marquage paresseux. Chaque type est initialement non affecté, mais plus tard si une instance du type est passée à une fonction attendant un trait Any, le type est affecté un TypeId.

Ou peut-être Rust met un TypeId sur chaque type que son instance est éventuellement passée à cette fonction? Je suppose que le premier serait cher.

43
Akangka

Tout d'abord, Rust n'a pas de réflexion; la réflexion implique que vous pouvez obtenir des détails sur un type au moment de l'exécution, comme les champs, les méthodes, les interfaces qu'il implémente, etc. Vous ne pouvez pas faire cela avec Rust. Le plus proche que vous pouvez obtenir est explicitement l'implémentation (ou la dérivation) un trait qui fournit cette information.

Chaque type reçoit un TypeId qui lui est affecté au moment de la compilation. Parce qu'avoir des ID globalement ordonnés est difficile , l'ID est un entier dérivé d'une combinaison de la définition du type et de métadonnées assorties sur la caisse dans laquelle il est contenu . En d'autres termes: ils ne sont assignés dans aucun ordre, ils sont juste des hachages des différents bits d'information qui entrent dans la définition le type. [1]

Si vous regardez le source pour le trait Any , vous verrez l'implémentation unique pour Any:

impl<T: 'static + ?Sized > Any for T {
    fn get_type_id(&self) -> TypeId { TypeId::of::<T>() }
}

(Les limites peuvent être informelles réduites à "tous les types qui ne sont pas empruntés à autre chose".)

Vous pouvez également trouver la définition de TypeId:

pub struct TypeId {
    t: u64,
}

impl TypeId {
    pub const fn of<T: ?Sized + 'static>() -> TypeId {
        TypeId {
            t: unsafe { intrinsics::type_id::<T>() },
        }
    }
}

intrinsics::type_id est une fonction interne reconnue par le compilateur qui, étant donné un type, renvoie son ID de type interne. Cet appel est simplement remplacé au moment de la compilation par l'ID de type entier littéral; il n'y a pas appel réel ici. [2] Voilà comment TypeId sait ce qu'est l'ID d'un type. TypeId, alors, est juste un wrapper autour de ce u64 pour masquer les détails de l'implémentation aux utilisateurs. Si vous le trouvez conceptuellement plus simple, vous pouvez simplement considérer le TypeId d'un type comme étant un entier 64 bits constant que le compilateur ( sait au moment de la compilation.

Any en avant à partir de get_type_id, qui veut dire get_type_id est vraiment juste la liaison de la méthode de trait à la TypeId::of méthode. Il est juste là pour vous assurer que si vous avez un Any, vous pouvez trouver le TypeId du type d'origine.

Maintenant, Any est implémenté pour la plupart des types , mais cela ne signifie pas que tous ces types ont en fait une implémentation Any flottant en mémoire. Ce qui se passe réellement, c'est que le compilateur génère uniquement le code réel pour l'implémentation Any d'un type si quelqu'un écrit du code qui l'exige. [3] En d'autres termes, si vous n'utilisez jamais l'implémentation Any pour un type donné, le compilateur ne la générera jamais.

Voici comment Rust remplit "ne payez pas pour ce que vous n'utilisez pas": si vous ne passez jamais un type donné comme &Any ou Box<Any>, alors le code associé n'est jamais généré et ne prend jamais de place dans votre binaire compilé.


[1]: Frustrant, cela signifie que le TypeId d'un type peut changer la valeur selon précisément comment la bibliothèque est compilée, au point que sa compilation en tant que dépendance (par opposition à une version autonome) entraîne la modification de TypeIds.

[2]: Pour autant que je sache. Je pourrais me tromper, mais je serais vraiment surpris si c'est le cas.

[3]: Ceci est généralement vrai pour les génériques dans Rust.

55
DK.