Rust 1.64 stabilise le trait IntoFuture. IntoFuture est un trait similaire à IntoIterator, mais plutôt que de prendre en charge les boucles for ... in ...,, IntoFuture modifie le fonctionnement de .await. Avec IntoFuture, le mot-clé .await peut attendre plus que des contrats à terme ; il peut attendre tout ce qui peut être converti en Future via IntoFuture - ce qui peut aider à rendre vos API plus conviviales !
Prenons par exemple un générateur qui construit des requêtes vers un fournisseur de stockage sur le réseau*:
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | pub struct Error { ... } pub struct StorageResponse { ... }: pub struct StorageRequest(bool); impl StorageRequest { /// Create a new instance of `StorageRequest`. pub fn new() -> Self { ... } /// Decide whether debug mode should be enabled. pub fn set_debug(self, b: bool) -> Self { ... } /// Send the request and receive a response. pub async fn send(self) -> Result<StorageResponse, Error> { ... } } |
L'utilisation typique ressemblerait probablement à ceci*:
Code Rust : | Sélectionner tout |
1 2 3 4 | let response = StorageRequest::new() // 1. create a new instance .set_debug(true) // 2. set some option .send() // 3. construct the future .await?; // 4. run the future + propagate errors |
Ce n'est pas mal, mais on peut faire mieux ici. En utilisant IntoFuture, nous pouvons combiner « construire le futur » (ligne 3) et « exécuter le futur » (ligne 4) en une seule étape*:
Code Rust : | Sélectionner tout |
1 2 3 | let response = StorageRequest::new() // 1. create a new instance .set_debug(true) // 2. set some option .await?; // 3. construct + run the future + propagate errors |
Nous pouvons le faire en implémentant IntoFuture pour StorageRequest. IntoFuture nous oblige à avoir un futur nommé que nous pouvons renvoyer, ce que nous pouvons faire en créant un "futur en boîte" et en définissant un alias de type pour celui-ci*:
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // First we must import some new types into the scope. use std::pin::Pin; use std::future::{Future, IntoFuture}; pub struct Error { ... } pub struct StorageResponse { ... } pub struct StorageRequest(bool); impl StorageRequest { /// Create a new instance of `StorageRequest`. pub fn new() -> Self { ... } /// Decide whether debug mode should be enabled. pub fn set_debug(self, b: bool) -> Self { ... } /// Send the request and receive a response. pub async fn send(self) -> Result<StorageResponse, Error> { ... } } // The new implementations: // 1. create a new named future type // 2. implement `IntoFuture` for `StorageRequest` pub type StorageRequestFuture = Pin<Box<dyn Future<Output = Result<StorageResponse, Error> + Send + 'static>> impl IntoFuture for StorageRequest { type IntoFuture = StorageRequestFuture; type Output = <StorageRequestFuture as Future>::Output; fn into_future(self) -> Self::IntoFuture { Box::pin(self.send()) } } |
Cela prend un peu plus de code à implémenter, mais fournit une API plus simple pour les utilisateurs.
À l'avenir, le groupe de travail Rust Async espère simplifier la création de nouveaux futurs nommés en prenant en charge impl Trait dans les alias de type (Type Alias Impl Trait ou TAIT). Cela devrait faciliter l'implémentation d'IntoFuture en simplifiant la signature de l'alias de type et la rendre plus performante en supprimant la Box de l'alias de type.
Pour mémoire, un trait définit la fonctionnalité qu'un type particulier possède et peut partager avec d'autres types. Nous pouvons utiliser des traits pour définir un comportement partagé de manière abstraite. Nous pouvons utiliser des limites de trait pour spécifier qu'un type générique peut être n'importe quel type qui a un certain comportement. Les traits sont similaires à une fonctionnalité souvent appelée interfaces dans d'autres langages, bien qu'avec quelques différences.
Dans l'exemple ci-dessous, nous définissons Animal, un groupe de méthodes. Le traitAnimal est alors implémenté pour le type Sheep, permettant l'utilisation des méthodes de Animal avec une instance du type Sheep.
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | struct Sheep { naked: bool, name: &'static str } trait Animal { // Méthode statique; `Self` fait référence au type ayant implémenté // le trait. fn new(name: &'static str) -> Self; // Méthode d'instance; Elles renverront une chaîne de caractères. fn name(&self) -> &'static str; fn noise(&self) -> &'static str; // Les traits peuvent fournir une implémentation par défaut. fn talk(&self) { println!("{} says {}", self.name(), self.noise()); } } impl Sheep { fn is_naked(&self) -> bool { self.naked } fn shear(&mut self) { if self.is_naked() { // Les méthodes de `Self` peuvent utiliser les méthodes déclarées // par le trait. println!("{} is already naked...", self.name()); } else { println!("{} gets a haircut!", self.name); self.naked = true; } } } // Implémentation des services du trait `Animal` // pour le type `Sheep`. impl Animal for Sheep { // En l'occurrence, `Self` fait référence à `Sheep`. fn new(name: &'static str) -> Sheep { Sheep { name: name, naked: false } } fn name(&self) -> &'static str { self.name } fn noise(&self) -> &'static str { if self.is_naked() { "baaaaah?" } else { "baaaaah!" } } // L'implémentation par défaut fournie par le trait // peut être réécrite. fn talk(&self) { // Par exemple, nous pourrions fournir une description plus précise. println!("{} pauses briefly... {}", self.name, self.noise()); } } fn main() { // Typer l'identificateur est nécessaire dans ce cas de figure. let mut dolly: Sheep = Animal::new("Dolly"); // TODO ^ Essayez de supprimer le type annoté. dolly.talk(); dolly.shear(); dolly.talk(); } |
Types FFI compatibles C dans le noyau et l'allocation
Lorsqu'il appelle ou est appelé par des ABI C, le code Rust peut utiliser des alias de type comme c_uint ou c_ulong pour faire correspondre les types correspondants de C sur n'importe quelle cible, sans nécessiter de code ou de conditions spécifiques à la cible.
Auparavant, ces alias de type n'étaient disponibles que dans std, de sorte que le code écrit pour les cibles intégrées et d'autres scénarios qui ne pouvaient utiliser que core ou alloc ne pouvait pas utiliser ces types.
Rust 1.64 fournit désormais tous les alias de type c_* dans core::ffi, ainsi que core::ffi::CStr pour travailler avec des chaînes C. Rust 1.64 fournit également alloc::ffi::CString pour travailler avec des chaînes C possédées en utilisant uniquement le crate alloc, plutôt que la bibliothèque std complète.
L'analyseur de Rust est maintenant disponible via rustup
rust-analyzer fait maintenant partie de la collection d'outils inclus avec Rust. Cela facilite le téléchargement et l'accès à l'analyseur de rouille, et le rend disponible sur plus de plates-formes.
La prochaine version de rustup fournira un proxy intégré afin que l'exécution de l'exécutable rust-analyzer lance la version appropriée.
La plupart des utilisateurs devraient continuer à utiliser les versions fournies par l'équipe de rust-analyzer (disponibles sur la page des versions de rust-analyzer), qui sont publiées plus fréquemment. Les utilisateurs de l'extension officielle VSCode ne sont pas affectés car elle télécharge et met à jour automatiquement les versions en arrière-plan.
Améliorations de Cargo : héritage de l'espace de travail et builds multi-cibles
Lorsque vous travaillez avec des collections de bibliothèques associées ou des crates binaires dans un espace de travail Cargo, vous pouvez désormais éviter la duplication des valeurs de champ communes entre les crates, telles que les numéros de version communs, les URL de référentiel ou rust-version. Cela permet également de synchroniser ces valeurs entre les crates lors de leur mise à jour.
Lors de la construction pour plusieurs cibles, vous pouvez désormais passer plusieurs options --target à cargo build, pour construire toutes ces cibles à la fois. Vous pouvez également définir build.target sur un tableau de plusieurs cibles dans .cargo/config.toml pour créer pour plusieurs cibles par défaut.
Source : Rust