
Qu'est-ce qu'une édition et en quoi est-elle importante ?
Selon l'équipe principale du langage, la sortie de Rust 1.0 a établi la "stabilité sans stagnation" comme un livrable essentiel de Rust. Depuis la version 1.0, la règle pour Rust a été qu'une fois qu'une fonctionnalité a été publiée dans la version stable, l'équipe s'engage à supporter cette fonctionnalité pour toutes les futures versions. Cependant, il est parfois utile de pouvoir apporter de petites modifications qui ne sont pas rétrocompatibles. L'exemple le plus évident est l'introduction d'un nouveau mot-clé, qui invaliderait les variables portant le même nom. Voici une illustration, selon l'équipe.
La première version de Rust n'avait pas les mots-clés async et await. Le changement soudain de ces mots en mots-clés dans une version ultérieure aurait cassé du code comme let async= 1;. Ainsi, les éditions sont le mécanisme par lequel l'équipe résout ce problème. Lorsqu'elle souhaite publier une fonctionnalité qui serait autrement incompatible avec le passé, elle le fait dans le cadre d'une nouvelle édition de Rust. Les éditions sont opt-in, et donc les crates existants ne voient pas ces changements jusqu'à ce qu'ils migrent explicitement vers la nouvelle édition.
Cela signifie que même la dernière version de Rust ne traitera toujours pas async comme un mot-clé, à moins que l'édition 2018 ou ultérieure ne soit choisie. Ce choix est fait par le crate dans le cadre de son Cargo.toml. Les nouveaux crates créés par cargo new sont toujours configurés pour utiliser la dernière édition stable.
Éditions, changements, rétrocompatibilité et migration
Selon l'équipe, les éditions ne divisent pas l'écosystème. « La règle la plus importante pour les éditions est que les crates d'une édition peuvent interagir de manière transparente avec les crates compilés dans d'autres éditions », explique-t-elle. Cela garantirait que la décision de migrer vers une édition plus récente est une décision "privée" que le crate peut prendre sans affecter les autres. L'exigence d'interopérabilité des crates implique donc certaines limites sur les types de changements qu'elle peut faire dans une édition. En général, les changements qui se produisent dans une édition ont tendance à être "superficiels".
« Tout le code Rust, quelle que soit l'édition, est finalement compilé dans la même représentation interne au sein du compilateur », note-t-elle. En outre, elle ajoute que la migration des éditions est facile et largement automatisée. « Notre objectif est de faciliter la mise à niveau des caisses vers une nouvelle édition », a-t-elle déclaré. « Lorsque nous publions une nouvelle édition, nous fournissons également des outils pour automatiser la migration. Il apporte à votre code les modifications mineures nécessaires pour le rendre compatible avec la nouvelle édition », a ajouté l'équipe.
Par exemple, selon elle, lors de la migration vers Rust 2018, il modifie tout ce qui est nommé async pour utiliser la syntaxe d'identifiant brut équivalente : r#async. Elle estime néanmoins que les migrations automatisées ne sont pas nécessairement parfaites : il peut y avoir des cas particuliers où des modifications manuelles sont encore nécessaires. L'outil s'efforce d'éviter les changements sémantiques qui pourraient affecter la correction ou les performances du code. En plus de l'outillage, elle maintient également un guide de migration d'édition qui couvre les changements qui font partie d'une édition.
Ce guide décrit le changement et indique où les gens peuvent en apprendre davantage à son sujet. Il couvre également tous les cas particuliers ou les détails que les gens doivent connaître. Le guide sert à la fois d'aperçu de l'édition, mais aussi de référence pour le dépannage rapide si les gens rencontrent des problèmes avec l'outillage automatisé.
Quels sont les changements prévus pour Rust 2021 ?
L'équipe a déclaré qu'au cours des derniers mois, le groupe de travail Rust 2021 a examiné un certain nombre de propositions concernant les éléments à inclure dans la nouvelle édition. Elle présente désormais la liste finale des changements retenus pour cette édition. Chaque fonctionnalité devait répondre à deux critères pour faire partie de cette liste. Premièrement, elle devait être approuvée par la ou les équipes Rust concernées. Deuxièmement, leur mise en œuvre devait être suffisamment avancée pour que l'équipe principale ait la certitude qu'elles sont terminées à temps pour les jalons prévus.
Ajouts a prelude
Prelude de la bibliothèque standard est le module contenant tout ce qui est automatiquement importé dans chaque module. Il contient des éléments couramment utilisés tels que Option, Vec, drop et Clone. L'équipe a expliqué que le compilateur Rust donne la priorité aux éléments importés manuellement sur ceux du prelude, afin de s'assurer que les ajouts au prelude ne cassent pas le code existant. Prenez un exemple où vous avez un crate ou un module appelé example contenant un pub struct Option.
Dans ce cas, use example::*; fera en sorte que Option se réfère sans ambiguïté à celui de example ; et non à celui de la bibliothèque standard. Cependant, l'ajout d'un trait au prelude peut casser le code existant d'une manière subtile. Un appel à x.try_into() utilisant un trait MyTryInto peut devenir ambigu et ne pas compiler si TryInto de std est également importé, puisqu'il fournit une méthode avec le même nom.
C'est la raison pour laquelle l'équipe n'a pas encore ajouté TryInto au prelude, car beaucoup de code serait cassé de cette façon. Comme solution, Rust 2021 utilisera un nouveau prelude. Il est identique à l'actuel, à l'exception de trois nouveaux ajouts :
- std::convert::TryInto ;
- std::convert::TryFrom ;
- std::iter::FromIterator.
Résolveur de fonctionnalités par défaut de Cargo
Depuis la version 1.51.0 de Rust, Cargo prend en charge un nouveau résolveur de fonctionnalités qui peut être activé avec resolver= "2" dans Cargo.toml. À partir de Rust 2021, ce sera la valeur par défaut. En d'autres termes, écrire edition= "2021" dans Cargo.toml impliquera resolver= "2". Le nouveau résolveur de fonctionnalités ne fusionne plus toutes les fonctionnalités demandées pour les crates qui sont dépendants de plusieurs façons.
IntoIterator pour les tableaux
Jusqu'à Rust 1.53, seules les références aux tableaux implémentent IntoIterator. Cela signifie que vous pouvez itérer sur &[1, 2, 3] et &mut [1, 2, 3], mais pas sur [1, 2, 3] directement. Selon l'équipe, il s'agit d'un problème de longue date, mais la solution n'est pas aussi simple qu'il paraît. L'ajout de l'implémentation du trait casserait le code existant. array.into_iter() compile déjà aujourd'hui parce qu'il appelle implicitement (&array).into_iter() en raison du fonctionnement de la syntaxe d'appel de méthode.
L'ajout de l'implémentation du trait en changerait la signification. Habituellement, l'équipe qualifie ce type de rupture de "mineure" et acceptable. Mais dans ce cas, il y a trop de code qui serait cassé par cela. Selon elle, il a été suggéré à plusieurs reprises de n'implémenter IntoIterator que pour les tableaux en Rust 2021. Cependant, cela n'est tout simplement pas possible. Vous ne pouvez pas avoir une implémentation de trait existant dans une édition et pas dans une autre, puisque les éditions peuvent être mélangées.
Au lieu de cela, l'équipe a décidé d'ajouter l'implémentation de trait dans toutes les éditions (à partir de Rust 1.53.0), mais d'ajouter un petit hack pour éviter la rupture jusqu'à Rust 2021. Dans le code Rust 2015 et 2018, le compilateur résoudra toujours array.into_iter() en (&array).into_iter() comme avant, comme si l'implémentation du trait n'existait pas. Cela s'applique uniquement à la syntaxe d'appel de la méthode .into_iter(). Cela n'affecte pas les autres syntaxes telles que for e in [1, 2, 3], iter.zip([1, 2, 3]) ou IntoIterator::into_iter([1, 2, 3]).
Celles-ci commenceront à fonctionner dans toutes les éditions. Selon l'équipe, bien qu'il soit dommage que cela nécessite un petit hack pour éviter la casse, elle se dit heureuse de la façon dont cette solution maintient la différence entre les éditions à un minimum absolu. Comme le hack n'est présent que dans les anciennes éditions, il n'y a pas de complexité supplémentaire dans la nouvelle édition.
Capture disjointe dans les closures
Les closures capturent automatiquement tout ce à quoi vous faites référence à l'intérieur de leur corps. Par exemple, || a + 1 capture automatiquement une référence à a à partir du contexte environnant. Actuellement, cela s'applique à des structures entières, même si elles n'utilisent qu'un seul champ. Par exemple, || a.x + 1 capture une référence à a et pas seulement à a.x. Selon l'équipe, dans certaines situations, cela pose un problème. Lorsqu'un champ de la structure est déjà emprunté ou déplacé, les autres champs ne peuvent plus être utilisés dans une fermeture,
Cela aura pour effet de capturer la structure entière, qui n'est plus disponible. À partir de Rust 2021, les fermetures ne captureront que les champs qu'elles utilisent. Ainsi, l'exemple ci-dessus compilera bien en Rust 2021. Ce nouveau comportement n'est activé que dans la nouvelle édition, car il peut changer l'ordre dans lequel les champs sont supprimés. Comme pour tous les changements d'édition, une migration automatique est disponible, qui mettra à jour vos closures pour lesquelles cela est important. Il peut insérer let _ = &a; à l'intérieur de la closure pour forcer la capture de la structure entière comme avant.
Cohérence de la macro panic
La macro panic!() est l'une des macros les plus connues de Rust. Cependant, l'équipe estime qu'elle présente quelques surprises subtiles qu'elle ne peut pas modifier pour des raisons de compatibilité ascendante. La macro panic!() n'utilise le formatage des chaînes que lorsqu'elle est invoquée avec plus d'un argument. Lorsqu'elle est invoquée avec un seul argument, elle ne regarde même pas cet argument. (Elle accepte même les non-chaînes de caractères comme panic!(123), ce qui est peu courant et rarement utile). Ceci serait particulièrement un problème une fois que les arguments de format implicites seront stabilisés.
Cette fonctionnalité fera de println!("hello {name}") un raccourci pour println!("hello {}", name). Cependant, panic!("hello {name}") ne fonctionnerait pas comme prévu, puisque panic!() ne traite pas un argument unique comme chaîne de format. Pour éviter cette situation confuse, Rust 2021 propose une macro panic!() plus cohérente. La nouvelle macro panic!() n'acceptera plus les expressions arbitraires comme seul argument. Tout comme println!(), elle traitera toujours le premier argument comme une chaîne de format.
Puisque panic!() n'acceptera plus de charges utiles arbitraires, panic_any() sera le seul moyen d'utiliser panic!() avec autre chose qu'une chaîne formatée. De plus, core::panic!() et std::panic!() seront identiques dans Rust 2021. Actuellement, il y a quelques différences historiques entre ces deux-là, qui peuvent être perceptibles lors de l'activation ou de la désactivation de #![no_std].
Réserver la syntaxe
Pour faire de la place pour une nouvelle syntaxe dans le futur, l'équipe a décidé de réserver la syntaxe pour les identificateurs et les littéraux préfixés : prefix#identifier, prefix "string", prefix "c", et prefix#123, où prefix peut être n'importe quel identificateur. (Sauf ceux qui ont déjà une signification, comme [I]b'....
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.