Rust 1.27 Release

L'équipe de développement de Rust est heureuse d'annoncer la sortie d'une nouvelle version de Rust: 1.27.0. Rust est un langage de programmation système destiné à la sécurité, la vitesse et l'exécution de code parallèle.


Si vous avez une version précédente de Rust installée à l'aide de rustup, alors pour mettre à niveau Rust vers la version 1.27.0, il vous suffit de faire:


$ rustup update stable 

Si vous n'avez pas encore installé rustup, vous pouvez l' installer à partir de la page correspondante de notre site Web. Des notes de version détaillées pour Rust 1.27.0 sont disponibles sur GitHub.


Nous souhaitons également attirer votre attention sur ce point: avant la sortie de la version 1.27.0, nous avons découvert une erreur dans les mappages de correspondance introduits dans la version 1.26.0, ce qui pouvait entraîner un comportement non correct. Comme il a été découvert très tard, déjà en train de publier cette version, bien qu'il soit présent depuis la version 1.26.0, nous avons décidé de ne pas interrompre la routine et de préparer une version fixe 1.27.1, qui sera publiée dans un proche avenir. Et en plus, si nécessaire, la version 1.26.3. Les détails peuvent être trouvés dans les notes de version correspondantes.


Ce qui est inclus dans la version stable 1.27.0


Dans ce numéro, deux améliorations importantes et tant attendues du langage sortent. Mais d'abord, un petit commentaire sur la documentation: la recherche est désormais disponible dans tous les livres de la bibliothèque Rust ! Par exemple, vous pouvez trouver «emprunter» dans le livre «Rust Programming Language» . Nous espérons que cela vous permettra de trouver plus facilement les informations dont vous avez besoin. De plus, un nouveau livre sur la rouille est paru . Ce livre explique comment utiliser directement rustc et comment obtenir d'autres informations utiles, telles qu'une liste de toutes les vérifications statiques.


SIMD


Donc, maintenant sur l'important: désormais, les fonctionnalités de base de l'utilisation de SIMD sont disponibles dans Rust! SIMD signifie "instruction unique, flux de données multiples" (instruction unique, données multiples). Considérez la fonction:


 pub fn foo(a: &[u8], b: &[u8], c: &mut [u8]) { for ((a, b), c) in a.iter().zip(b).zip(c) { *c = *a + *b; } } 

Ici, nous prenons deux tranches entières, additionnons leurs éléments et mettons le résultat dans la troisième tranche. Le code ci-dessus illustre la façon la plus simple de le faire: vous devez parcourir l'ensemble des éléments, les assembler et enregistrer le résultat. Cependant, les compilateurs trouvent souvent une meilleure solution. LLVM «vectorise automatiquement» souvent un code similaire, où une telle formulation complexe signifie simplement «utilise SIMD». Imaginez que les tranches a et b comportent 16 éléments, les deux. Chaque élément est u8 , ce qui signifie que les tranches contiendront chacune 128 bits de données. À l'aide de SIMD, nous pouvons placer les deux tranches a et b dans des registres de 128 bits, les ajouter avec une instruction, puis copier les 128 bits résultants vers c . Cela fonctionnera beaucoup plus vite!


Malgré le fait que la version stable de Rust ait toujours pu tirer parti de la vectorisation automatique, le compilateur n'est parfois tout simplement pas assez intelligent pour comprendre qu'il peut être appliqué dans ce cas. De plus, tous les processeurs ne prennent pas en charge ces fonctionnalités. Par conséquent, LLVM ne peut pas toujours les utiliser, car votre programme peut s'exécuter sur diverses plates-formes matérielles. Par conséquent, dans Rust 1.27, avec l'ajout du module std::arch , il est devenu possible d'utiliser directement ce type d'instructions, c'est-à-dire que maintenant nous ne sommes plus obligés de compter uniquement sur une compilation intelligente. De plus, nous avons la possibilité de choisir une implémentation spécifique, en fonction de différents critères. Par exemple:


 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))] fn foo() { #[cfg(target_arch = "x86")] use std::arch::x86::_mm256_add_epi64; #[cfg(target_arch = "x86_64")] use std::arch::x86_64::_mm256_add_epi64; unsafe { _mm256_add_epi64(...); } } 

Ici, nous utilisons les drapeaux cfg pour sélectionner la version correcte du code en fonction de la plate-forme cible: sur x86 sa propre version sera utilisée, et sur x86_64 la sienne. Nous pouvons également choisir au moment de l'exécution:


 fn foo() { #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { if is_x86_feature_detected!("avx2") { return unsafe { foo_avx2() }; } } foo_fallback(); } 

Ici, nous avons deux versions de la fonction: l'une utilise AVX2 - un type spécifique de SIMD qui vous permet d'effectuer des opérations 256 bits. La macro is_x86_feature_detected! va générer un code qui vérifie si le processeur prend en charge AVX2, et si c'est le cas, la fonction foo_avx2 sera appelée. Sinon, nous recourrons à une implémentation sans AVX, foo_fallback . Notre code fonctionnera donc très rapidement sur les processeurs qui prennent en charge AVX2, mais il fonctionnera également sur d'autres processeurs, quoique plus lentement.


Tout semble un peu bas et inconfortable - oui, ça l'est! std::arch sont les primitives de ce genre de choses. Nous espérons qu'à l'avenir nous stabiliserons toujours le std::simd avec des capacités de haut niveau. Mais l'émergence de capacités SIMD de base vous permet désormais d'expérimenter la prise en charge de haut niveau de diverses bibliothèques. Par exemple, consultez le package le plus rapide . Voici un extrait de code sans SIMD:


 let lots_of_3s = (&[-123.456f32; 128][..]).iter() .map(|v| { 9.0 * v.abs().sqrt().sqrt().recip().ceil().sqrt() - 4.0 - 2.0 }) .collect::<Vec<f32>>(); 

Pour utiliser SIMD dans ce code avec faster , vous devez le changer comme ceci:


 let lots_of_3s = (&[-123.456f32; 128][..]).simd_iter() .simd_map(f32s(0.0), |v| { f32s(9.0) * v.abs().sqrt().rsqrt().ceil().sqrt() - f32s(4.0) - f32s(2.0) }) .scalar_collect(); 

Cela ressemble presque à la même chose: simd_iter au lieu d' iter , simd_map au lieu de map , f32s(2.0) au lieu de 2.0 . Mais au final, vous obtenez une version certifiée SIMD de votre code.


En plus de cela , vous ne pouvez jamais écrire cela vous-même, mais, comme toujours, les bibliothèques dont vous dépendez peuvent le faire. Par exemple, le support a déjà été ajouté regex , et sa nouvelle version aura une accélération SIMD sans que vous ayez à faire quoi que ce soit!


dyn Trait


En fin de compte, nous avons regretté la syntaxe initialement sélectionnée des objets trait dans Rust. Comme vous vous en souvenez, pour un Foo vous pouvez définir un objet trait comme celui-ci:


 Box<Foo> 

Cependant, si Foo était une structure, cela signifierait simplement placer la structure à l'intérieur d'une Box<T> . Lors du développement du langage, nous avons pensé que de telles similitudes seraient une bonne idée, mais l'expérience a montré que cela conduit à la confusion. Et ce n'est pas seulement Box<Trait> : impl SomeTrait for SomeOtherTrait également une syntaxe formellement correcte, mais vous devez presque toujours écrire impl<T> SomeTrait for T where T: SomeOtherTrait place. C'est la même chose avec impl SomeTrait , qui semble ajouter des méthodes ou une implémentation par défaut possible au type, mais en fait, il ajoute ses propres méthodes à l'objet type. Enfin, par rapport à la syntaxe impl Trait récemment ajoutée, la syntaxe Trait semble plus courte et préférable à utiliser, mais en réalité ce n'est pas toujours vrai.


Par conséquent, dans Rust 1.27, nous avons stabilisé la nouvelle syntaxe dyn Trait . Les objets caractéristiques ressemblent maintenant à ceci:


 //  =>  Box<Foo> => Box<dyn Foo> &Foo => &dyn Foo &mut Foo => &mut dyn Foo 

De même pour les autres types de pointeurs: Arc<Foo> maintenant Arc<dyn Foo> , etc. En raison de l'exigence de compatibilité descendante, nous ne pouvons pas supprimer l'ancienne syntaxe, mais nous avons ajouté une vérification statique de l' bare-trait-object , qui par défaut résout l'ancienne syntaxe. Si vous souhaitez l'interdire, vous pouvez activer cette vérification. Nous pensions qu'avec la vérification activée par défaut, trop d'avertissements seront désormais affichés.


Soit dit en passant, nous travaillons sur un outil appelé rustfix , qui peut automatiquement mettre à jour votre code vers des idiomes plus récents. Il utilisera des contrôles statiques similaires pour cela. Restez à l'écoute pour les annonces rustfix dans les prochaines annonces.

#[must_use] pour les fonctions


En conclusion, l'effet de l'attribut #[must_use] été étendu: il peut désormais être utilisé pour les fonctions .


Auparavant, il ne s'appliquait qu'aux types, tels que Result <T, E> . Mais maintenant, vous pouvez le faire:


 #[must_use] fn double(x: i32) -> i32 { 2 * x } fn main() { double(4); // warning: unused return value of `double` which must be used let _ = double(4); // (no warning) } 

Avec cet attribut, nous avons également légèrement amélioré la bibliothèque standard : Clone::clone , Iterator::collect et ToOwned::to_owned si vous n'utilisez pas leurs valeurs de retour, ce qui vous aidera à remarquer des opérations coûteuses dont vous ignorez accidentellement les résultats.


Voir les notes de version pour plus de détails.


Stabilisation de bibliothèque


Les nouvelles API suivantes ont été stabilisées dans cette version:



Voir les notes de version pour plus de détails.


Améliorations du fret


Cargo a reçu deux améliorations mineures dans cette version. Tout d'abord, un --target-dir , qui peut être utilisé pour changer le répertoire d'exécution cible.


De plus, l'approche de Cargo sur la façon de gérer les cibles a été finalisée. Cargo essaie de détecter des tests, des exemples et des exécutables dans votre projet. Cependant, une configuration explicite est parfois requise. Mais dans la mise en œuvre initiale, c'était problématique. Disons que vous avez deux exemples et que Cargo les détecte tous les deux. Vous souhaitez configurer l'un d'eux, pour lequel vous ajoutez [[example]] à Cargo.toml pour spécifier des exemples de paramètres. Actuellement, Cargo verra que vous avez défini l'exemple de manière explicite et ne tentera donc pas de détecter automatiquement les autres. C'est un peu bouleversant.


Par conséquent, nous avons 'auto'- Cargo.toml . Nous ne pouvons pas résoudre ce problème sans une panne possible des projets qui en dépendent par inadvertance. Par conséquent, si vous souhaitez configurer certains objectifs, mais pas tous, vous pouvez définir la clé autoexamples sur true dans la section [package] .


Voir les notes de version pour plus de détails.


Développeurs 1.27.0


Beaucoup de gens ont participé au développement de Rust 1.27. Nous n'aurions pas pu terminer le travail sans chacun de vous.


Je vous remercie!


De la part d'un traducteur: j'exprime ma gratitude aux membres de la communauté ruRust et personnellement ozkriff pour leur aide dans la traduction et la relecture

Source: https://habr.com/ru/post/fr415091/


All Articles