Résumé
Le C++ et le Rust joueront tous deux un rôle important dans la programmation des systèmes dans un avenir prévisible. Avec un tel degré de chevauchement dans l'applicabilité, l'utilisation conjointe des deux langages est essentielle à la recherche de la sécurité et de la performance qui sont maintenables et évolutives. L'objectif principal de l'initiative est de permettre aux technologues de choisir le langage le mieux adapté à leur situation et de minimiser les coûts et les risques liés à l'interopérabilité.
Bien que l'interopérabilité du C ait été au centre des préoccupations de Rust depuis sa création, plusieurs facteurs ont empêché la mise en place d'une solution mature, standard et automatique pour le développement de logiciels utilisant conjointement C++ et Rust. Pour surmonter ces obstacles, l'initiative poursuivra une approche descendante, axée sur l'espace-problème, afin de faciliter la coopération et le consensus entre les parties prenantes, notamment le projet Rust, les organisations membres de la Fondation Rust et la communauté des individus et des organisations utilisant C++ ou Rust. Les ressources matérielles apportées à l'initiative seront utilisées pour poursuivre trois stratégies simultanées :
- Améliorer les outils existants et résoudre les problèmes tactiques au sein du projet Rust afin de réduire les frictions et les risques liés à l'interopérabilité à court terme.
- Construire un consensus autour d'objectifs à long terme nécessitant des changements dans le projet Rust lui-même et développer des approches tactiques pour commencer à les poursuivre.
- S'engager avec la communauté et le comité C++ pour améliorer la qualité de l'interopérabilité pour les deux langages afin d'atteindre les objectifs mutuels de sécurité et de performance.
L'approche axée sur les problèmes est essentielle pour surmonter les obstacles dans ce domaine. Beaucoup de travail a été fait sur les outils qui apportent une valeur considérable, mais il y a une limite à ce qui peut être réalisé avec des bibliothèques externes et des outils de génération de code ; motiver avec succès des changements plus profonds nécessite un consensus stratégique afin de définir un travail tactique concret. Par conséquent, le présent document ne définit pas de solutions ; il constitue un appel à la contribution et à la participation pour affiner ces stratégies et les tactiques qui en découlent. Le reste du document décrit plus en détail le problème lui-même et ces stratégies.
Le problème
L'interopérabilité C++/Rust est la capacité d'échanger des données et d'exécuter du code écrit dans les deux langages. Il existe un compromis inhérent entre la simplicité et l'efficacité et une dichotomie distincte entre la communication inter-processus et l'interopérabilité intra-processus. Cette dernière se produit au sein du même exécutable, soit par la compilation intégrée de plusieurs langages (inline embedding), soit par des interfaces de fonctions étrangères (FFI). L'initiative interop est exclusivement axée sur cette dernière forme, de sorte que toutes les mentions ultérieures de l'interopérabilité dans le présent document se réfèrent à l'interopérabilité intra-processus. De nombreuses questions ouvertes ne sont pas abordées ici, telles que les caractéristiques des langages Rust et C++ qui peuvent être efficacement prises en charge par les interfaces de fonctions étrangères et l'incidence de l'établissement de liens statiques ou dynamiques sur l'interopérabilité.
Qui est concerné ?
Bien que Rust et C++ soient tous deux utilisés en dehors de leur domaine principal en tant que langages de systèmes, ce type d'interopérabilité est particulièrement pertinent dans le domaine des systèmes où les ressources sont limitées. Le désir d'interopérabilité dépend du système en question, mais les cas d'utilisation les plus courants sont les suivants :
- les systèmes C++ qui ajoutent ou remplacent des fonctionnalités par Rust
- Systèmes Rust utilisant du code C++ existant
- Systèmes polyglottes supportant Rust (comme pour les architectures de plugins)
En raison de cette dynamique et de la longue histoire du C++ avant Rust, le premier cas est de loin le plus courant.
Comme il n'existe actuellement aucune chaîne d'outils permettant de mélanger C++ et Rust dans le même fichier source, les approches existantes en matière d'interopérabilité se concentrent sur les solutions basées sur la FFI. L'interopérabilité FFI s'effectue aujourd'hui via les ABI de plate-forme C, parce qu'elles sont moins dépendantes de la plate-forme et plus stables que les ABI de plate-forme C++. Ainsi, plus on s'appuie sur des facilités spécifiques au C++, plus il est difficile de les traduire efficacement à travers une interface basée sur le C.
Quand et où cela est-il pertinent ?
À l'approche du dixième anniversaire de sa version stable 1.0, Rust passe de la phase des « early adopters » à celle de la « early majority » dans le cycle de vie de l'adoption des technologies. Cette transition correspond à l'importance croissante accordée par l'industrie et les gouvernements à l'adoption de langages à mémoire sécurisée, et Rust se distingue comme un choix de premier ordre. Rust combine de manière unique une prise en charge robuste de la concurrence et une garantie d'absence de comportement non défini, ce qui en fait une alternative convaincante au C++ dans les environnements où le ramasse-miettes n'est pas possible. Sa fiabilité éprouvée dans ces domaines fait de Rust un outil essentiel pour des systèmes plus sûrs et plus performants.
Alors que Rust a considérablement évolué au cours de cette période, l'histoire de l'interopérabilité avec le C++ reste limitée par les fondements de l'ABI C. Une grande partie du développement a été consacrée aux bibliothèques pour faciliter l'interopérabilité avec les deux langages. Une quantité importante de développement a été consacrée aux bibliothèques pour faciliter l'interopérabilité avec C et C++, mais au niveau du langage et du compilateur, la situation reste largement inchangée par rapport aux premiers jours de Rust.
Alors que le désir d'intégrer Rust dans un plus grand nombre de codes C++ augmente, l'intérêt de rendre l'interopérabilité C++/Rust plus sûre, plus facile et plus efficace s'accroît rapidement. Bien que chaque langage adopte une approche globale différente, tous deux considèrent la sécurité comme une préoccupation essentielle dans les systèmes modernes. Rust et C++ disposent tous deux de facilités au niveau du langage et de la bibliothèque standard pour améliorer la sécurité de manière apparemment compatible, mais des avantages significatifs sont perdus lors du passage de la frontière FFI en utilisant l'ABI C.
Quel est l'impact ?
En tant que langage système, Rust a toujours été conçu pour être interopérable avec C. Même pour les langages non systèmes, C est la lingua franca pour la FFI en général et l'accès aux ressources au niveau du système d'exploitation en particulier. En tant que telle, l'interopérabilité C ↔︎ Rust est relativement simple et la charge qui pèse sur le programmeur Rust se présente sous deux formes principales :
- La communication est limitée aux interfaces exprimables dans le système de types C, ce qui exclut une grande partie des avantages de Rust en termes d'ergonomie et de sécurité.
- La frontière de la FFI elle-même n'est généralement pas sûre, ce qui signifie que des garanties telles que l'absence de comportement non défini et de course aux données sont perdues.
Les défis d'interopérabilité auxquels sont confrontés les développeurs de Rust se retrouvent chez les développeurs C++, qui doivent également sacrifier des caractéristiques de sécurité et d'ergonomie qui ne sont pas propres au C. La relation superset entre le C++ et le C simplifie une partie de la traduction des types d'interface, mais tous les aspects bénéfiques propres au C++ sont perdus. Même une base de code qui utilise de manière experte les caractéristiques modernes du C++ pour améliorer la sécurité et les performances doit introduire une région de code plus subtile et plus dangereuse autour du point de FFI. Il en résulte à la fois un effort supplémentaire de la part du développeur et une réduction de la qualité de la base de code pour soutenir l'interopérabilité.
La conséquence de ce coût d'interopérabilité accru est que les bases de code C++ et Rust sont moins capables d'accéder à du code précieux qui existe déjà dans l'autre langage, et que la capacité de transition des composants du système d'un langage à l'autre est réduite en dehors des limites d'interface de type C existantes. En fin de compte, cette réduction de la liberté conduit à des résultats moins bons pour tous les utilisateurs, car les technologues sont moins libres de choisir les solutions les plus efficaces.
Pourquoi cela se produit-il ?
Le langage C existe depuis le début des années 70 et le langage C++ depuis le milieu des années 80. Alors pourquoi, quatre décennies plus tard, n'y a-t-il pas de meilleur moyen d'interopérer avec C++ ? Cela s'explique en partie par le fait que toute interopérabilité est un effort bilatéral, de sorte que la qualité de l'interface dépend des langages des deux côtés. L'interface doit être définie dans des termes que les deux langages peuvent exprimer. Le fait que ces termes soient natifs du langage (comme les interfaces C pour C++) peut rendre le processus plus ou moins commode, mais la richesse fondamentale de l'interface dépend de sa capacité à exprimer la sémantique des constructions de chaque langage. Tout ce qui n'est pas nativement représentable dans l'interface doit être communiqué par une transformation bien définie vers et depuis l'interface, mais il doit également y avoir des constructions compatibles de part et d'autre. Par définition, l'interopération a lieu entre des langues différentes, de sorte que, comme pour les problèmes de traduction dans les langues naturelles, il y aura toujours une certaine perte sémantique due à la traduction.
Pourquoi la situation n'est-elle pas déjà meilleure ? Le C++ était déjà très mature à l'époque où Rust a été créé, et Rust a vu le jour au sein de Mozilla, aux côtés d'une base de code C++ de plusieurs millions de lignes, vieille de plusieurs décennies, avec l'intention explicite d'être une alternative au C++. Hormis le C, aucun des principaux langages de systèmes développés avant le C++ n'a connu d'utilisation moderne significative. Parmi les langages développés après le C++, seuls quelques-uns ont fait l'objet d'une utilisation commerciale significative : Go, Rust et Swift. L'histoire de l'interopérabilité du C++ pour tous ces langages est instructive, mais l'utilisation prévue et la gouvernance de Go et de Swift sont très différentes de celles de Rust. L'élément social est peut-être le plus important : bien qu'initialement développé dans le cadre de la recherche Mozilla, le projet Rust a été dirigé par des bénévoles et l'innovation par le biais d'un processus RFC qui est plutôt ascendant, alors que les entreprises à l'origine de Go et de Swift ont continué à être plus actives dans la gestion de ces langages et ont poursuivi l'interopérabilité qui s'aligne sur les objectifs de leurs langages. Conformément à l'éthique collaborative et au modèle de gouvernance de la communauté Rust, l'approche typique de Rust a été de donner la priorité aux solutions dans les bibliothèques externes tout en minimisant les changements dans le langage et la bibliothèque standard. Cette approche est évidente dans l'histoire de l'interopérabilité C++ de Rust, où la plupart des travaux ont été réalisés dans des bibliothèques externes. Les responsables du projet Rust connaissent bien cette dynamique, et ce document est une tentative d'explorer l'espace du problème d'une manière collaborative et stratégique.
La réalité actuelle
La situation de l'interopérabilité entre C++ et Rust est aujourd'hui très différente de celle qui prévalait lors de la création de Rust. Bien qu'il existe de nombreux autres outils, les trois plus populaires ont été publiés à plusieurs années d'intervalle sur crates.io :
- bindgen (appeler C depuis Rust) : 2015
- cbindgen (appeler Rust depuis C) : 2017
- cxx (C++ ↔︎ Rust) : 2020
Bien que les trois soient toujours activement maintenus, les deux premiers sont plus complets en raison de leur histoire beaucoup plus longue et de leur portée plus réduite. En tant que crate la plus populaire ciblant l'interopérabilité du C++, cxx est résolument plus ambitieuse et également moins complète. L'objectif déclaré de cxx est de fournir un moyen sûr d'interopérer avec C++ avec un surcoût négligeable. Ceci est basé sur la reconnaissance du fait que C++ et Rust partagent beaucoup plus de concepts de haut niveau que l'un ou l'autre ne le fait avec C. Le but est de décrire cette frontière de langage en Rust et C++, ce qui fournit plus de fidélité que l'ABI C utilisé par bindgen et cbindgen. Il s'agit d'une direction prometteuse et le potentiel de cette approche mérite d'être exploré plus avant. En même temps, il y a beaucoup de limitations et de types Rust fondamentaux qui ne sont pas encore implémentés. Enfin, la bibliothèque est auto-décrite comme « restrictive et avec des opinions » et son but n'est pas de fournir une interopérabilité arbitraire, mais plutôt de fournir une abstraction plus sûre et de plus haut niveau pour combler le fossé conceptuel plus étroit entre ces deux langages.
À l'heure actuelle, l'interopérabilité entre C++ et Rust est meilleure qu'elle ne l'a jamais été, mais il existe encore des défis importants qui servent à limiter la faisabilité de l'introduction de Rust dans les bases de code C++ existantes ainsi qu'à restreindre son utilisation à des frontières bien définies plutôt qu'à une interopérabilité arbitraire. Enfin, il convient de mentionner que l'intégration avec les systèmes de construction constitue un défi particulier pour l'interopérabilité, mais qu'une stratégie pour l'aborder doit encore être développée. Il est probable que des approches bénéfiques émergeront au fur et à mesure que les stratégies au niveau de l'interface seront affinées en travaux tactiques.
Conséquences
Toute utilisation de la technologie implique des compromis et l'adoption de toute nouvelle technologie entraîne des frictions, d'abord pour que les technologues apprennent à l'utiliser et ensuite pour l'intégrer dans des systèmes où l'interopérabilité avec les technologies existantes est nécessaire. Il n'est ni possible ni souhaitable de remplacer tout le code C++ par du Rust. La détermination du moment où la réécriture ou l'augmentation des systèmes C++ avec du code Rust entraîne un bénéfice net nécessite des analyses par système. Indépendamment des choix de mise en œuvre spécifiques, l'objectif de la technologie est d'améliorer la vie des gens. À cette fin, la réduction des frictions inutiles et la maximisation de la liberté dans les choix des technologues sont conformes à l'initiative d'interopérabilité et à l'intérêt général.
Le(s) objectif(s)
Conformément à la vision énoncée au début, l'objectif est de rendre l'interopérabilité « accessible et abordable », mais ce à quoi cela ressemble diffère beaucoup en fonction du public. Une façon de conceptualiser le summum de l'accessibilité est de considérer l'idéal d'une interopérabilité sans friction : qu'il s'agisse de Rust ou de C++, l'utilisation du code dans l'autre langage n'est pas plus difficile que s'il était implémenté dans le même langage. Le fait que chaque langage ait des abstractions fondamentalement différentes signifie que cet objectif n'est pas plus réalisable qu'une traduction parfaite entre les langues naturelles, mais il s'agit néanmoins d'un idéal utile car il peut fournir une direction à suivre et ainsi identifier les objectifs en cours de route ainsi que les obstacles qui interviennent.
Voici quelques idéaux d'interopérabilité sans friction qui méritent d'être poursuivis :
- Un minimum de travail : la quantité de code supplémentaire à écrire par l'utilisateur n'est pas supérieure à celle de la langue maternelle.
- Pas de complexité supplémentaire : les détails de l'interface elle-même ne sont pas pertinents pour le code de part et d'autre de l'interface.
- Sécurité maximale : l'interopérabilité doit nécessiter un minimum de code non sécurisé.
- Correction maximale : l'utilisation incorrecte des facilités d'interopérabilité elles-mêmes devrait être une erreur au moment de la compilation18
- Performance maximale : l'interopérabilité doit aspirer au principe de l'absence de surcharge.
À ce stade, il est prématuré de distiller ces qualités en objectifs tactiquement réalisables, mais, considérée comme une grande stratégie, l'interopérabilité sans friction peut être poursuivie au moyen de trois stratégies parallèles :
- à court terme : améliorer les outils existants, résoudre les problèmes qui ont été bloqués en raison d'un manque d'appropriation, d'un effort concerté ou d'une hésitation à stabiliser une mise en œuvre
- à long terme : jeter les bases d'une forme plus riche d'interopérabilité au niveau du langage, du compilateur et de la bibliothèque standard.
- Interopérabilité sociale : s'engager avec la communauté C++, y compris ses utilisateurs et ses processus de normalisation, pour construire le pont des deux côtés et améliorer simultanément les deux langages.
Dans le cadre de toutes les stratégies, l'initiative s'appuiera sur plusieurs axiomes de conception :
- Construire les fondations d'un avenir meilleur tout en améliorant activement le présent.
- Rechercher une interopérabilité de haute qualité des deux côtés
- Rechercher une interopérabilité générale (non liée à une chaîne d'outils ou à une interface utilisateur spécifique).
- Éviter les changements à Rust lui-même qui mineraient ses valeurs fondamentales
- Ne modifier le langage ou la bibliothèque standard que lorsque les solutions des bibliothèques externes sont insuffisantes.
Conclusions et prochaines étapes
En tant qu'énoncé du problème et vision stratégique, ce document décrit le travail futur de l'initiative C++/Rust Interop de la Fondation Rust. Il sert à assurer la transparence et à encourager la contribution et la collaboration avec le projet Rust ainsi qu'avec les communautés Rust et C++. Plus concrètement, il sert de structure pour allouer les ressources matérielles, financières et humaines de l'initiative. En fin de compte, l'orientation de l'interopérabilité C++/Rust sera déterminée par les actions de nombreuses personnes dans les deux communautés et un travail important sur les efforts à court et à long terme est en cours. À ce jour, peu de travaux ont été menés pour faciliter la coopération sociale entre les deux langages, et bien que de meilleures solutions pour les utilisateurs des deux langages soient plus susceptibles d'émerger de cette stratégie, les trois stratégies sont indépendantes et se renforcent mutuellement. Voici les prochaines étapes pour chaque stratégie :
Court terme
Le plus grand avantage qui peut être réalisé à court terme sera de s'appuyer sur les solutions qui ont déjà fait leurs preuves et qui sont utilisées dans la pratique aujourd'hui. La distillation de cette stratégie en tactiques viables impliquera 3 éléments :
- Identifier les problèmes dans les bibliothèques, Rust lui-même ou les dépendances qui entravent les efforts actifs d'interopérabilité aujourd'hui.
- Donner la priorité aux questions qui représentent une bonne proposition de valeur pour les ressources matérielles, techniques ou sociales à la disposition de l'initiative.
- Appliquer les ressources en dotant la Fondation en personnel ou en soutenant matériellement des technologues externes.
A long terme
Pour améliorer considérablement l'interopérabilité, les améliorations progressives ne suffisent pas. La stratégie à long terme visera donc à se rapprocher concrètement de l'idéal d'une interopérabilité sans friction. Cela nécessite une interface fondamentalement plus riche entre les langages, axée sur l'espace conceptuel partagé entre C++ et Rust, avec un accent sur la sécurité et l'efficacité. Pour être utile, cela nécessite une large adhésion et une coopération entre les différentes équipes du projet. Les éléments de cette stratégie sont les suivants :
- Établir des relations et recueillir les contributions et le soutien des principales parties prenantes au projet et d'autres experts compétents.
- Déterminer une structure viable pour discuter et prendre des décisions de haut niveau sur les éléments fondamentaux nécessaires à la construction d'une expérience d'interopérabilité plus riche et sur les voies qui ne seront pas poursuivies.
- Une fois que les éléments fondamentaux exploitables sont déterminés, rechercher le soutien d'équipes individuelles (peut-être par le biais d'objectifs de projet) et allouer des ressources par le biais de subventions de la Fondation.
Le premier élément est en cours et sera maintenu. Le deuxième élément nécessite une approche descendante, axée sur l'espace-problème, ce qui n'est pas très conventionnel pour le projet Rust et la communauté, mais il semble que l'on soit favorable à l'amélioration de cette capacité. Il s'agit là d'un des principaux défis et avantages potentiels de l'initiative. Le troisième élément dépend quelque peu du succès de la première itération des objectifs du projet, mais il semble jusqu'à présent bien adapté au soutien de nouveaux travaux importants entre le projet et la fondation.
Interopérabilité sociale
La troisième stratégie est basée sur la reconnaissance du fait que si Rust a été inventé avec l'intention de fournir une alternative plus sûre à C++, les deux langages continueront d'exister dans un avenir prévisible et une interopérabilité de haute qualité tendra à améliorer les projets qui utilisent les deux langages. Une certaine compétitivité entre les langages qui visent des cas d'utilisation similaires est inévitable, mais il y a plus à gagner de la coopération car l'innovation technologique n'est pas un jeu à somme nulle. Les éléments de cette stratégie sont les suivants :
- Établir des relations et recueillir les commentaires et le soutien des principaux acteurs des organismes de normalisation du C++ et d'autres experts compétents.
- Faciliter les présentations et les discussions entre les membres des deux communautés afin d'identifier les valeurs communes et les stratégies mutuellement bénéfiques.
- Déterminer si une coopération formelle peut être établie et travailler pour la soutenir grâce aux ressources et aux membres de la Fondation.
Des trois stratégies, celle-ci est peut-être la plus ambitieuse et la moins sûre. C'est aussi celle dont la maturation sera probablement la plus longue. Le travail a donc déjà commencé, mais aucune des autres stratégies n'en dépend.
Prochaines étapes
Cet énoncé de problème et les stratégies associées constituent la première étape vers l'engagement des communautés Rust et C++ au sens large. Il reste encore beaucoup de choses à décider et les nouvelles idées ainsi que les contributions constructives seront extrêmement précieuses.
Partager