Notions avancées en Rust
Introduction
Le propos du présent ouvrage est d’aborder tous les aspects qui font Rust, pour permettre ainsi au lectorat d’être le plus rapidement possible autonome sur ce langage. Cela implique de passer brièvement, voire d’ignorer certains aspects non essentiels pour suivre le fil conducteur choisi.
Le présent chapitre tente de réparer un peu les effets de ce parti-pris. Il s’agira donc d’évoquer ici des aspects qui n’ont pu l’être auparavant. Ainsi, ce chapitre peut apparaître comme une boîte à outils complémentaire du langage Rust.
Les objets-traits
1. Présentation
Le langage Rust est multiparadigme, et l’un de ces paradigmes est la programmation orientée objet. Or, l’un des principes de la programmation orientée objet est le polymorphisme, c’est-à-dire concrètement, en développement logiciel, la capacité (entre autres) de surcharger une méthode ou une fonction.
Or, la surcharge de fonctions peut se réaliser de deux façons différentes :
-
Une surcharge que l’on peut qualifier de statique, que nous allons expliquer immédiatement après.
-
Une surcharge qualifiée de dynamique qui, elle, implique en Rust l’usage d’objets-traits.
2. La surcharge statique
Si on devait l’expliquer en une phrase, ce serait celle-ci : la surcharge statique consiste pour la compilation à générer une implémentation de surcharge de fonction par type concerné. Il s’agit typiquement de ce que l’on peut trouver en C++, c’est donc une solution éprouvée qui fonctionne bien. En revanche, à chaque type sera associée une implémentation dédiée, ce qui implique un poids du binaire qui augmentera forcément.
Pour illustrer ce point, prenons l’exemple suivant :
use std::fmt::Display;
fn afficher_valeur<T:Display>(valeur : T){
println!("Affichage de la valeur : {} ", valeur);
}
fn main(){
afficher_valeur(42 as u64);
afficher_valeur(3.14159);
afficher_valeur("Bonjour tout le monde.".to_string());
}
Dans cet exemple, la fonction générique afficher_valeur affiche effectivement une valeur passée en paramètre de type T.
fn afficher_valeur<T:Display>(valeur : T){
println!("Affichage de la valeur : {} ", valeur);
}
Dans la fonction main, on utilise cette fonction avec trois types différents : entier, flottant et chaîne de caractères....
Le code Rust non sûr
1. Introduction
Voici une autre notion susceptible d’être rencontrée au gré de la lecture de codes, ou même nécessaire dans certains contextes de développement. Comme précédemment, un mot-clé permet d’identifier que vous êtes en présence d’un code Rust non sûr (unsafe) : le mot-clé unsafe. Le lien suivant correspond à la page de documentation officielle de ce mot-clé : https://doc.rust-lang.org/std/keyword.unsafe.html
Ce mot-clé unsafe permet d’indiquer ceci au compilateur : « n’applique pas les règles de sécurité habituelles, relatives au langage Rust ». La compilation permet donc dans ce cas précis de faire des choses habituellement interdites en langage Rust.
Les actions qui, concrètement, peuvent faire l’objet d’un traitement non sûr sont (entre autres) les suivantes :
-
Déréférencer un pointeur brut (action rendue strictement impossible par le compilateur).
-
Modifier une variable statique mutable (également impossible en théorie : une telle variable est perçue en Rust comme une constante).
-
Implémenter des traits non sécurisés.
D’autres actions non sécurisées existent, mais les trois citées ici sont sans doute les principales.
2. Déréférencer un pointeur brut
On commence par créer un projet dédié :
cargo new unsafe_exemples --bin
> Created binary (application) `unsafe_exemples` package
En Rust, la référence sur une variable possède en quelque sorte la valeur. Il n’existe en théorie aucun moyen d’accéder, autrement qu’à travers cette référence, à la valeur de la variable....