web-dev-qa-db-fra.com

Quelles sont les similitudes et les différences entre les concepts de C ++ et les traits de Rust?

Dans Rust, le principal outil d'abstraction est traits. En C++, il existe deux outils pour les abstractions: les classes abstraites et les modèles. Pour se débarrasser de certains des inconvénients de l'utilisation de modèles (par exemple, des messages d'erreur difficiles à lire), C++ a introduit concepts qui sont "ensembles nommés d'exigences" .

Les deux fonctionnalités semblent pour être assez similaires:

  • La définition d'un trait/concept se fait en énumérant les exigences.
  • Les deux peuvent être utilisés pour limiter/restreindre les paramètres de type générique/modèle.
  • Les traits de rouille et les modèles C++ avec des concepts sont tous deux monomorphisés (je sais Rust peuvent également être utilisés avec la répartition dynamique, mais c'est une autre histoire).

Mais d'après ce que je comprends, il existe également des différences notables. Par exemple, les concepts de C++ semblent définir un ensemble d'expressions qui doivent être valides au lieu de lister les signatures de fonction. Mais il y a beaucoup d'informations différentes et déroutantes (peut-être parce que les concepts ne se posent qu'en C++ 20?). C'est pourquoi j'aimerais savoir: quelles sont exactement les différences et les similitudes des concepts de C++ et des traits de Rust?

Y a-t-il des fonctionnalités qui ne sont offertes que par des concepts ou des traits? Par exemple. qu'en est-il des types et consts associés de Rust? Ou délimiter un type par plusieurs traits/concepts?

42

Avertissement: je n'ai pas encore utilisé de concepts, tout ce que je sais à leur sujet a été glané des différentes propositions et références, alors prenez cette réponse avec un grain de sel.

Polymorphisme d'exécution

Les traits de rouille sont utilisés à la fois pour le polymorphisme à la compilation et, parfois, pour le polymorphisme à l'exécution; Les concepts ne concernent que le polymorphisme au moment de la compilation.

Structurel vs nominal.

La plus grande différence entre les concepts et les traits est que les concepts utilisent typage structurel tandis que les traits utilisent typage nominal:

  • En C++, un type ne satisfait jamais explicitement un Concept; il peut "accidentellement" le satisfaire s'il s'avère qu'il satisfait à toutes les exigences.
  • In Rust une construction syntaxique spécifique impl Trait for Type est utilisé pour indiquer explicitement qu'un type implémente un trait.

Il y a un certain nombre de conséquences; en général, le typage nominal est meilleur du point de vue de la maintenabilité - en ajoutant une exigence à un trait - tandis que le typage structurel vaut mieux un pontage de bibliothèques tierces - un type de la bibliothèque A peut satisfaire un concept de la bibliothèque B sans qu'ils soient conscients les uns des autres.

Contraintes

Les traits sont obligatoires:

  • Aucune méthode ne peut être appelée sur une variable de type générique sans que ce type soit requis pour implémenter un trait fournissant la méthode.

Les concepts sont entièrement facultatifs:

  • Une méthode peut être appelée sur une variable de type générique sans que ce type soit requis pour satisfaire un Concept, ni contraint de quelque manière que ce soit.
  • Une méthode peut être appelée sur une variable de type générique satisfaisant un Concept (ou plusieurs) sans que cette méthode soit spécifiée par un Concept ou une Contrainte.
  • Les contraintes (voir note) peuvent être entièrement ad hoc et spécifier des exigences sans utiliser un concept nommé; et encore une fois, ils sont entièrement facultatifs.

Remarque: une contrainte est introduite par une clause requires et spécifie des exigences ad hoc ou des exigences basées sur des concepts.

Exigences

L'ensemble des exigences exprimables est différent:

  • Les concepts/contraintes fonctionnent par substitution, donc autorisez toute l'étendue des langues; les exigences incluent: types/constantes/variables imbriqués, méthodes, champs, capacité à être utilisée comme argument d'une autre fonction/méthode, capacité à être utilisée comme argument générique d'un autre type, et leurs combinaisons.
  • Les traits, en revanche, ne permettent qu'un petit ensemble d'exigences: les types/constantes associés et les méthodes.

Sélection de surcharge

La rouille n'a pas de concept de surcharge ad hoc, la surcharge ne se produit que par les traits et la spécialisation n'est pas encore possible.

Les contraintes C++ peuvent être utilisées pour "ordonner" les surcharges du moins spécifique au plus spécifique, afin que le compilateur puisse automatiquement sélectionner la surcharge la plus spécifique pour laquelle les exigences sont satisfaites.

Remarque: avant cela, SFINAE ou la répartition des balises seraient utilisées en C++ pour effectuer la sélection; la gymnastique était nécessaire pour travailler avec des ensembles de surcharge ouverts.

Disjonction

Comment utiliser cette fonctionnalité n'est pas encore très clair pour moi.

Les mécanismes d'exigence dans Rust sont purement additifs (conjonctions, aka &&), en revanche, en C++ requires, les clauses peuvent contenir des disjonctions (aka ||).

23
Matthieu M.