Newer
Older
\documentclass[11pt]{article}
\usepackage[xetex, scale=0.8]{geometry}
\usepackage{enumitem}
\usepackage{hyperref}
\usepackage{titlesec}
\newcommand{\sectionbreak}{\clearpage}
\renewcommand*\contentsname{Table des matières}
\setlength{\parindent}{0pt}
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhead[L, R]{}
\fancyhead[C]{L'essentiel du langage Rust (\textit{Ébauche})}
\fancyfoot[C]{Vincent Lafeychine}
\renewcommand{\headrulewidth}{1pt}
\renewcommand{\footrulewidth}{.2pt}
\setlength{\headheight}{15pt}
\usepackage{listings, listings-rust}
\lstdefinestyle{Rust}{
style=boxed,
basicstyle={\footnotesize\ttfamily},
columns=fullflexible,
texcl
}
\begin{document}
\tableofcontents
Rust est un langage de programmation système, compilé et multi-paradigme.
\begin{itemize}
\item Langage compilé : le code source n'est pas exécutable tel quel, il faut d'abord le
transformer avec un compilateur.
\item Langage de programmation système : très peu de fonctionnalités sont implémentés dans
les binaires résultants du langage.
en général il ne se passe rien qui n'ait été explicitement demandé dans le code source.
Par exemple, il n'y a pas de ramasse-miettes ou d'allocateur automatique.
\item Langage multi-paradigme : le langage prend beaucoup de concepts des langages
concurrents, orientés objets et fonctionnel.\\
Par exemple :
\begin{itemize}
\item tout comme dans les langages orientés objets, la plupart du code se fait
en définissant des structures de données et des méthodes qui opèrent dessus.
\item tout comme dans les langages fonctionnels, tout est une expression
(voir \Cref{expressionsEverywhere})
\end{itemize}
L'objectif du Rust est de ne sacrifier aucun des trois critères :
rapidité, sécurité, et concurrence.
Pour ce faire, la syntaxe de Rust est enrichie pour permettre au compilateur
d'avoir l'ensemble des informations nécessaires : il y aura souvent plus d'indications de types
à écrire qu'en OCaml et plus d'opérations à rendre explicite (conversions de type,
mutabilité, durées de vies) qu'en C/C++.
L'environnement de Rust se compose de beaucoup d'outils aidant le développement ainsi
que le déploiement.
Le compilateur Rust fonctionne principalement comme les autres compilateurs.
Cependant, il est rare de l'utiliser directement car \texttt{cargo} permet
souvent de s'affranchir d'invoquer explicitement \texttt{rustc}.
Voici un exemple de compilation d'un fichier nommé \texttt{source.rs} vers
un binaire exécutable nommé \texttt{bin} en activant les optimisations :
\texttt{rustc source.rs -o bin -O}
\texttt{cargo} est un outil de build, comme peut l'être \texttt{make}, mais comporte
beaucoup plus de fonctionnalités :
\item téléchargement automatique des dépendances externes
\item compilation pour le développement et pour la production
(le premier est plus rapide à compiler mais est moins rapide à l'exécution)
\item création de la documentation
\item lancement des tests
\item appel de commandes externes
Le linter lit le code et recherche des patterns qui sont...
\begin{itemize}
\item ...souvent des indices d'un bug\\
(variable inutilisée, conversion de type inutile, code inaccessible, ...)
\item ...considérés comme de mauvaises pratiques\\
(trop de variables à une lettre, ...)
\item ...connus pour être moins performants qu'une alternative\\
(copies profondes inutiles, pointeurs inutiles, structures de données peu
efficaces en mémoire, ...)
\end{itemize}
On peut lancer un linter basique avec \texttt{cargo check}, et \texttt{cargo clippy} en
est la version plus poussée.
On trouve à l'addresse \href{https://rust-lang.github.io/rust-clippy/master/index.html}{https://rust-lang.github.io/rust-clippy/master/index.html}
une liste exhaustive des vérifications de \texttt{clippy} (pas toutes activées par défaut),
avec des explications de pourquoi le pattern est dangereux ou considéré mauvais.
\subsection{\texttt{rls} : Language Server Protocol}
L'ensemble des IDE migrent vers le protocole Open-Source de Microsoft : Language Server Protocol.
Ce protocole permet d'avoir les fonctionnalités principales d'un IDE (auto-complétion, saut vers les définitions...) par le biais d'un serveur propre au langage.
Formatage du code afin que tout le monde puisse lire, écrire, et maintenir aisément du code.
Il est la plupart du temps invoqué par \texttt{cargo fmt}.
Il existe plusieurs sources d'informations pour apprendre le Rust :
\item \href{https://doc.rust-lang.org/book/}{RustBook} : apprendre par la lecture
\item \href{https://github.com/rust-lang/rustlings/}{Rustlings} : apprendre par la résolution d'exercices
\item \href{https://doc.rust-lang.org/stable/rust-by-example/}{Rust by example} : apprendre par de nombreux exemples de cas d'utilisations
Quelques autres liens utiles :
\begin{itemize}
\item \href{https://play.rust-lang.org/}{Playground} : pour faire des tests rapides
dans le navigateur ou regarder l'assembleur généré
\item \href{https://this-week-in-rust.org/}{This Week in Rust} : newsletter officielle
\item \href{https://github.com/rust-lang/rust/}{github:rust} : le code source intégral de
\texttt{rustc} et de la librairie standard
\item \href{https://crates.io}{Crates} : le dépôt de paquets officiel depuis lequel sont
téléchargées les dépendances
\item \href{https://docs.rs/}{Docs} : la documentation officielle de tous les paquets
\end{itemize}
\subsection{Hello World!}
\begin{lstlisting}[style=Rust, language=Rust]
Pour compiler ce programme on va directement utiliser \texttt{rustc}, avec la commande
shell suivante :
\texttt{rustc hello\_world.rs}
Le binaire généré sera nommé en fonction du fichier si on ne précise pas explicitement
un autre nom avec l'option \texttt{-o BINAIRE}.
On peut ensuite exécuter normalement \texttt{./hello\_world} qui affichera le texte
\texttt{Hello World} (avec un retour à la ligne).
\subsection{Quelques notions basiques}
Comme C, Rust a de nombreux types numériques de différentes tailles. Contrairement à C
les noms sont unifiés : \texttt{i}/\texttt{u}/\texttt{f} pour ``integer'', ``unsigned integer'',
``floating point'', suivi du nombre de bits.
\item entiers signés : \texttt{i8}, \texttt{i16}, \texttt{i32}, \texttt{i64} et \texttt{i128}.
\item entiers non signés : \texttt{u8}, \texttt{u16}, \texttt{u32}, \texttt{u64} et
\texttt{u128}.
\item flottants : \texttt{f32}, \texttt{f64}.
\item entiers de la taille de la machine : \texttt{isize} et \texttt{usize}.\\
(sont égaux à des \texttt{i32}/\texttt{u32} sur système 32 bits, et
\texttt{i64}/\texttt{u64} sur système 64 bits)
\end{itemize}
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
// Par défaut, les variables sont non-modifiables (tout comme un \texttt{let} en OCaml).
let x: i8 = 1; // x vaut 1
// \texttt{let} peut redéfinir une variable qui écrase la précédente
let x = x + 1; // x vaut 2, le type est inféré à partir de l'expression
{
// une variable est visible uniquement à l'intérieur du bloc qui la définit
let x = 5; // x vaut 5
let y = 4; // y vaut 4
}
// x vaut de nouveau 2
// y n'existe plus
// pour mettre à jour une variable d'une manière visible en dehors du bloc,
// il faut la déclarer \texttt{mut} (mutable)
// Le mot clé \textbf{mut} permet de rendre modifiable une variable.
let mut z: i32 = 1; // similaire à \texttt{let z = ref 1} en OCaml
z = 4;
// \textit{Note :} ici le linter génère un avertissement que z n'a pas besoin
// d'être mutable et que sa première valeur 1 est inutile.
// z vaut 4
{
z += 1;
}
// z vaut 5
// Appel de fonction
// (la fonction peut être définie plus tard dans le fichier,
// il n'est pas nécessaire de la déclarer)
let result = add(x, y); // ici aussi le type est deviné grâce à la signature de add
// \textit{Note :} dans ce document on explicitera les types plus que nécessaire
// la plupart des fonctions en Rust peuvent s'écrire avec pour unique annotation
// de type la signature de la fonction.
println!("Result: {}", result); // "Result: 5"
}
// La fonction prend deux arguments qui sont des \textbf{i32} et retourne un \textbf{i32}.
fn add(x: i32, y: i32) -> i32 {
x + y // pas besoin de mot-clé return, la dernière expression est renvoyée
Pour une explication plus poussée des différents types de chaînes de caractères,
voir \Cref{advancedStrings}.
Les chaînes de caractères en Rust sont toutes encodés en \texttt{UTF-8}, et
prennent principalement l'un des deux formats suivants :
\item chaîne de caractères allouée dynamiquement (type \texttt{String}).
\item référence non modifiable, nommée slice (type \texttt{\&str}).
\end{itemize}
\begin{lstlisting}[style=Rust, language=Rust]
// On peut créer une String à partir d'une \&str
let hello_world_string: String = hello_world.to_string();
// alternative équivalente : String::from(hello\_world)
// Réciproquement on peut convertir une String en \&str
// soit tout entière :
let hello_world_slice: &str = &hello_world_string;
// soit partiellement, ici seulement les caractères 6 à 10 inclus.
let world_slice: &str = &hello_world_string[6..11];
println!("Hi {}", world_slice); // "Hi World"
}
\end{lstlisting}
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
Il y a des choses qu'on peut ou ne peut pas faire avec ces deux formats :
\begin{itemize}
\item on ne peut pas modifier une \texttt{\&str}, alors que \texttt{String}
a des méthodes pour être modifiée
\item on ne peut pas additionner (concaténer) deux \texttt{String} alors qu'on peut
additionner \texttt{\&str + \&str} ou encore \texttt{String + \&str}
\end{itemize}
Pour beaucoup de raisons il y a dans la librairie standard des fonctions qui demandent
l'un ou l'autre type. Heureusement la conversion est facile.
On verra souvent des erreurs de compilation comme celle-ci :
\begin{lstlisting}[style=Rust]
Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/main.rs:4:14
|
4 | hello += world;
| ^^^^^
| |
| expected `&str`, found struct `String`
| help: consider borrowing here: `&world`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error
\end{lstlisting}
Il suffit de remplacer la ligne en \texttt{hello += \&world;} pour que cela fonctionne.
\newpage
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
let mut hello = String::new(); // nouvelle String vide
hello.push_str("Hello "); // concatène du texte à hello
hello += "World"; // += est équivalent à push\_str
hello.push('!'); // concatène un unique caractère
// ('' est la notation pour le type \texttt{char})
println!("{}", hello); // "Hello World!"
}
\end{lstlisting}
La manière standard de formatter du texte est la famille de macros \texttt{format!}.
(pour plus de détails sur les macros en général, voir \Cref{advancedMacros})
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
print!("{} {} {} ", 1, '2', "trois"); // affiche "1 2 trois" sans retour à la ligne
println("quatre {} {}", 5, 6.0); // affiche "quatre 5 6" avec un retour à la ligne
eprint!("erreur ");
eprintln!("grave"); // pareil mais sur la sortie stderr
let formatteur: std::fmt::Formatter = ...; // comment obtenir un formatteur est au delà
// des objectifs de cette section, on montre
// juste qu'il s'utilise de manière similaire
write!(formatteur, "truc");
writeln!(formatteur, "bidule");
let s: String = format!("{} + {} = {}", 1, 2, 3);
// s vaut "1 + 2 = 3"
// À chaque fois on peut préciser le format d'affichage dans les \{\}
let hello = "hello";
println!("{hello}"); // équivalent de println!("{}", hello)
println!("{hello:#?}"); // affichage de debug, écrit "hello" en incluant les guillemets
println!("{hello: >10}"); // aligne à une largeur de 10 en rajoutant des espaces
// et beaucoup d'autres, parmi lesquels :
// - comment afficher le signe + ou -
// - comment représenter les flottants
// - etc...
}
\end{lstlisting}
Comme pour les chaînes de caractères, les tableaux se présentent sous plusieurs formes.
Tous peuvent être modifié avec \texttt{tab[i] = v;} (à condition bien sûr qu'ils
soient déclarés \texttt{mut}).
\begin{itemize}
\item \texttt{Vec<Type>} : un tableau dynamique qui contient des éléments de type \texttt{T}.\\
Peut être redimensionné avec \texttt{push}.\\
Equivalent de \texttt{String} : un \texttt{String} est presque un
\texttt{Vec<char>} avec quelques méthodes en plus.\\~\\
D'ailleurs pour convertir de l'un à l'autre on dispose de\\
\texttt{let chaine: String = vecteur.iter().collect();}\\
\texttt{let vecteur: Vec<char> = chaine.chars().collect();}
\item \texttt{[Type; Taille]} (e.g. \texttt{[u64; 5]}) : un tableau de taille fixée connue.
\item \texttt{\&[Type]} : un tableau de taille fixée inconnue.\\
Équivalent de \texttt{\&str}.
\end{itemize}
Ici encore on peut dans une certaine mesure convertir d'un type à un autre.
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
let vector: Vec<usize> = vec![1, 2, 3];
let array: [usize; 3] = [1, 2, 3];
let slice_of_full_vector: &[usize] = &vector;
let slice_of_partial_vector: &[usize] = &vector[..2];
let slice_of_full_array: &[usize] = &array;
let vector_of_array = array.to_vec();
let vector_of_slice = &array[1..].to_vec();
\section{Références}
Les références remplacent la plupart du temps la notion de pointeur.
En plus de pointer vers une donnée parfois modifiable, elles évitent des copies
inutiles et peuvent garantir qu'il n'y a pas plusieurs personnes qui ont un droit
d'écriture en même temps.
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
let dix: usize = 10;
let dix_ref: &usize = &dix; // dix\_ref pointe vers dix
println("{}", *dix_ref); // 10
let mut un: usize = 0; // un est modifiable
{
let un_mut_ref: &mut usize = &mut un; // référence avec droit d'écriture sur un
// un ne peut pas être lu ni écrit pendant
// que un\_mut\_ref existe
*un_mut_ref = 1; // modifie la valeur de un
// un += 1; // erreur
// let x = un; // erreur
} // un\_mut\_ref cesse d'exister, on peut de nouveau lire et écrire dans un
{
let un_ref: &usize = &un; // référence avec droit de lecture seulement
// un peut être lu mais pas écrit pendant que
// un\_ref existe
// un += 1; // erreur
// let x = un; // ok
} // un\_ref cesse d'exister, on peut de nouveau écrire dans un
// Les références sont surtout utiles pour appeler des fonctions
let mut x: usize = 1;
incrementer(&mut x); // permet de donner un droit d'écriture sur une variable locale
println!("La nouvelle valeur de x est {}", x); // 2
let v: Vec<usize> = vec![1, 2, 3, 4, 5];
let s = somme(&v); // droit de lecture uniquement, permet de ne pas avoir à copier v
println!("La somme de {:#?} est {}", s); // 15
// On a la garantie que v a toujours exactement le même contenu qu'avant l'appel à la fonction
}
fn incrementer(ptr: &mut usize) {
*ptr += 1;
}
fn somme(vect: &Vec<usize>) -> usize {
vect.iter().sum()
La forme complète d'une référence est \texttt{\&'lft Type} ou \texttt{\&'lft mut Type} :
voir \Cref{advancedLifetimes} pour plus de détails.
\section{Conditions et boucles}
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
let age: u32 = 15;
if age < 18 {
println!("Tu es mineur(e)");
} else {
println!("Tu es majeur(e)");
} // "Tu es mineur(e)"
}
\end{lstlisting}
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
// On va itérer sur chacun des éléments du tableau
for elem in array.iter() { // équivalent: for elem in \&array
print!("{} ", elem);
} // affiche "a b c d "
for (index, elem) in array.iter().enumerate() {
print!("{}:{} ", index, elem);
} // affiche "0:a 1:b 2:c 3:d "
for index in 0..array.len() {
print!("{}:{} ", index, array[elem]);
} // idem
for elem in array.into_iter() { // équivalent: for elem in array
print!("{} ", elem);
} // cette boucle semble très similaire à la première,
// mais il y a deux différences :
// - elem est de type char au lieu de \&char
// - array n'est plus utilisable ensuite
// On va modifier (de manière impérative) chaque élément du tableau
let mut vector: Vec<i32> = vec![10, 20, 30];
for elem in vector.iter_mut() { // équivalent: for elem in \&mut vector
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
*elem += 50;
}
println!("{:?}", vector); // "[60, 70, 80]"
}
\end{lstlisting}
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
let mut age = 15;
while age < 18 {
println!("Tu es encore trop jeune.");
age += 1;
}
println!("Tu as bien l'âge requis maintenant.");
}
\end{lstlisting}
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
// La syntaxe \textbf{loop} est équivalente à \textbf{while true}
loop {
println!("Hello!");
// Keyword \textbf{break}, comme en C
break;
}
}
\end{lstlisting}
Il se trouve que les boucles et tests sont des expressions: voir \Cref{expressionsEverywhere}.
\section{Structures, Enums et Pattern matching}
Il y a trois formes pour déclarer un type qui est la concaténation de
valeurs de plusieurs types.
Tous peuvent être déstructurés similairement.
Ici on veut que le type \texttt{T} ait trois valeurs de types \texttt{u8}, \texttt{char},
\texttt{String}.
Tuples:
type T = (u8, char, String);
fn main() {
let t = (1, 'a', "un".to_string());
let (n, c, s) = t;
let n = t.0;
// avantages: peut être créé à la volée
// inconvénients: long à écrire si il y a beaucoup de valeurs, on risque d'oublier des valeurs
// utilisation: pour des types qui n'ont pas particulièrement de signification dans le programme
\end{lstlisting}
~\\
Tuple struct:
\begin{lstlisting}[style=Rust, language=Rust]
struct T(u8, char, String);
fn main() {
let t = T(1, 'a', "un".to_string());
let T(n, c, s) = t;
let n = t.0;
}
// avantages: ne peut pas être confondu avec d'autres types qui ont les mêmes éléments
// inconvénients: on peut oublier l'ordre des éléments
// utilisation: pour des types qui contiennent peu de valeurs
\end{lstlisting}
Voir \Cref{patternNewtype} pour une utilisation fréquente de cette forme
~\\
Struct:
\begin{lstlisting}[style=Rust, language=Rust]
struct T {
num: u8,
chr: char,
str: String,
let t = T {
num: 1,
chr: 'a',
str: "un".to_string(),
let T { num: n, chr: c, str: s } = t;
let n = t.num;
// avantages: on ne risque pas d'oublier la signification de chaque champ
// inconvénients: long à écrire
// utilisation: pour des types qui ont soit beaucoup de valeurs,
// soit plusieurs valeurs qu'on risque de confondre
\end{lstlisting}
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
\subsection{Type somme}
Pour un type qui est l'union de plusieurs types, on utilise une \texttt{enum}.
Les variants peuvent eux-mêmes être des types produits.
\begin{lstlisting}[style=Rust, language=Rust]
enum U {
Zero,
Un(u8),
Deux(u8, char),
Trois {
n: u8,
c: char,
f: f64,
},
}
fn main() {
let u0 = U::Zero;
let u1 = U::Un(1);
let u2 = U::Deux(2, 'b');
let u3 = U::Trois {
n: 3,
c: 'c',
f: 3.0,
};
match u3 with {
U::Zero => println!("zero"),
U::Un(n) => println!("un:{}", n),
U::Deux(n, c) => println!("deux:{},{}", n, c),
U::Trois { n, c, f } => println!("trois:{},{},{}", n, c, f),
\subsection{Un exemple concret}
Dans l'exemple ci-dessous, nous allons créer deux utilisateurs, Anthony et Luc.
Chaque utilisateur a :
\begin{itemize}
\item un identifiant
\item un prénom
\item potentiellement, un surnom
\end{itemize}
enum Nickname { // L'enum \textbf{MaybeNickname} se compose :
Some(String), // - soit d'un élément, qui est la chaîne de caractère.
None, // - soit d'aucun élément.
}
fn main() {
let user_one: User = User {
id: 1,
name: "Anthony".to_string(),
};
let user_two: User = User {
id: 2,
name: "Luc".to_string(),
};
print_user(user_one); // "1: Anthony aka Antho"
print_user(user_two); // "2: Luc"
}
fn print_user(user: User) {
match user.nickname {
Nickname::Some(nickname) => println!("{}: {} aka {}", user.id, user.name, nickname),
Nickname::None => println!("{}: {}", user.id, user.name),
L'exemple précédent fonctionne correctement, cependant l'enum représentant une valeur
optionelle est un pattern très fréquent. On ne va pas créer une nouvelle enum pour chaque
valeur non obligatoire.
On peut rendre le type générique pour résoudre ce problème:
enum Option<T> { // L'enum \textbf{Option<T>} se compose :
Some(T), // - soit d'un élément, qui est de type T.
None, // - soit d'aucun élément.
type Nickname = Option<String>;
// tout le reste du code est inchangé
\vspace{.5cm}
En fait \texttt{Option} est déjà définie dans la librairie standard,
exactement sous cette forme :
\href{https://doc.rust-lang.org/src/core/option.rs.html#514-523}{option.rs}.
En plus de cela, les variants \texttt{None} et \texttt{Some} sont déjà importés
donc on peut écrire directement \texttt{None} plutôt que \texttt{Option::None}.
De manière générale, dès qu'un objet est accessible via une notation
\texttt{truc::machin}, on peut ajouter une ligne \texttt{use truc::machin;} pour
qu'à présent l'objet soit accessible via \texttt{machin}.
Si on ajoute une ligne \texttt{use Option::*;}, on pourra donc aussi omettre le
\texttt{Option::} sur notre propre type \texttt{Option}.
Afin de contraindre l'accessibilité à notre donnée, il est possible d'utiliser des
méthodes.
Cette fonctionnalité de Rust permet de découper correctement une architecture afin
de regrouper les fonctions.
Dans l'exemple, on va ajouter la fonction \texttt{print} sur notre structure \texttt{User}.
\begin{lstlisting}[style=Rust, language=Rust]
// On va implémenter des fonctions pour la structure \textbf{User}.
impl User {
// La fonction \textbf{print} prend l'utilisateur en paramètre
// invocation :
// user.print();
Some(nickname) => println!("{}: {} aka {}", self.id, self.name, nickname),
None => println!("{}: {}", self.id, self.name),
// Une manière de créer un utilisateur sans surnom
// cette fonction ne prend pas de paramètre self.
// invocation :
// let antho: User = User::new(0, "Anthony".to\_string());
fn new(id: usize, name: String) -> Self {
// le type Self est un alias pour le type dont on est
// en train d'implémenter des méthodes
User {
id, // équivalent à id: id
name,
nickname: None,
}
}
// Une méthode qui modifie l'utilisateur.
// Elle requiert que l'utilisateur soit déclaré mut
// invocation :
// let mut antho = User::new(0, "Anthony".to\_string());
// antho.set\_nickname("Antho".to\_string());
fn set_nickname(&mut self, nickname: String) {
self.nickname = Some(nickname);
}
}
\end{lstlisting}
\section{Traits}
Les traits sont des ensembles de méthodes que les types sur lesquels ils sont appliqués doivent implémenter.
Par exemple, pour utiliser \texttt{println!}, il faut que le type implémente l'ensemble des méthodes du trait \texttt{fmt::Display} pour pouvoir afficher.
Le trait \texttt{fmt::Display} ne contient qu'une seule méthode : \texttt{fmt}.
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
\begin{lstlisting}[style=Rust, language=Rust]
use std::fmt; // Permet d'écrire fmt::xxx au lieu de std::fmt::xxx
struct User {
id: i32,
name: String,
nickname: Option<String>,
}
// Pour utiliser \textbf{{}} dans \textbf{println!}, le trait \textbf{fmt::Display} doit être implémenté.
impl fmt::Display for User {
// La signature de \textbf{fmt} doit être pareille que celle définie par le trait.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.nickname {
Option::Some(nickname) => write!(f, "{}: {} aka {}", self.id, self.name, nickname),
Option::None => write!(f, "{}: {}", self.id, self.name),
}
}
}
fn main() {
let user_one: User = User {
id: 1,
name: "Anthony".to_string(),
nickname: Option::Some("Antho".to_string()),
};
let user_two: User = User {
id: 2,
name: "Luc".to_string(),
nickname: Option::None,
};
println!("{}", user_one); // "1: Anthony aka Antho"
println!("{}", user_two); // "2: Luc"
}
\end{lstlisting}
Ainsi, l'exemple ci-dessus peut utiliser \texttt{println!} directement.
Tous les types primaires de Rust implémentent le trait \texttt{fmt::Display}.
\newpage
La structure de donnée \texttt{Box} permet de stocker un type d'objet, mais \texttt{Box} permet aussi de contenir un trait de l'objet.
\begin{lstlisting}[style=Rust, language=Rust]
use std::fmt;
// Le keyword \textbf{dyn} permet d'utiliser un trait
fn drawer(vector: Vec<Box<dyn fmt::Display>>) {
for elem in vector {
println!("{}", elem);
}
}
fn main() {
drawer(vec![
Box::new("Hello"), // "Hello"
Box::new(12), // "12"
Box::new(Box::new(24)), // "24"
]);
}
\end{lstlisting}
\section{Gestion des erreurs}
\begin{itemize}
\item Erreurs fatales, non-rattrapables, qui va libérer toutes les variables et fermer le programme.
\item Erreurs rattrapables, dont il faudra gérer l'échec.
\end{itemize}
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
let vector = vec![1, 2, 3];
// Accès à un élément non-présent dans le vecteur.
v[5]; // Rust va générer une erreur fatale grâce à \textbf{panic!}
}
\end{lstlisting}
Pour les erreurs non-fatales, Rust a la structure de donnée suivante :
\begin{lstlisting}[style=Rust, language=Rust]
enum Result<T, E> {
Ok(T),
Err(E),
}
\end{lstlisting}
Utilisons cette structure afin de faire un programme pour lire un fichier :
\begin{lstlisting}[style=Rust, language=Rust]
use std::fs::File; // Permet d'écrire \textbf{File::open} au lieu de \textbf{std::fs::File::open}
use std::io; // Permet d'écrire \textbf{io::Error} au lieu de \textbf{std::io::Error}
use std::io::Read; // Permet d'utiliser la fonction \textbf{read\_to\_string()}
fn main() {
match read_file() {
Ok(contents) => println!("{}", contents), // Affiche le contenu du fichier.
Err(e) => eprintln!("Error: {}", e), // Affiche l'erreur rencontrée.
}
}
fn read_file() -> Result<String, io::Error> {
let file = File::open("hello.txt");
let mut file = match file {
Ok(file) => file, // Le fichier n'a pas eu de problème pour être ouvert :
// on renvoie la structure du fichier.
Err(e) => return Err(e), // Le fichier a eu un problème pour être ouvert :
// on renvoie l'erreur.
};
let mut s = String::new();
return match file.read_to_string(&mut s) {
Ok(_) => Ok(s), // Le fichier n'a pas eu de problème pour être lu :
// on renvoie le contenu du fichier.
Err(e) => Err(e), // Le fichier a eu un problème pour être lu :
// on renvoie l'erreur.
};
}
\end{lstlisting}
\section{Appartenance (\textit{Ownership})}
Tous les programmes doivent gérer la façon dont ils utilisent la mémoire.
\begin{itemize}
\item Certains langages, comme Java ou Go, utilisent un ramasse-miette détectant la mémoire qui n'est plus utilisée.
\item D'autres langages, comme le C, requiert que le développeur explicite l'allocation et la désallocation de la mémoire.
\end{itemize}
Rust utilise une autre approche : la mémoire est gérée par un système d'appartenance que le compilateur va vérifier à la compilation.
\item chaque valeur en Rust a une variable correspondante, appelée le propriétaire (owner).
\item il ne peut y avoir qu'un seul propriétaire à la fois.
\item quand le propriétaire sort du scope, la valeur va être libérée.
\end{itemize}
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
{
// \textbf{s} est une chaîne de caractères explicitement alloué dans le tas
let s = String::from("Hello world");
// Utilisation de \textbf{s}
println!("{}", s); // "Hello world"
} // \textbf{s} est libéré
// \textbf{s} n'est plus valide: Il est impossible de l'utiliser
}
\end{lstlisting}
La libération se fait grâce à l'appel de \texttt{drop()}, cette fonction sur la structure \texttt{String()} permet de libérer la mémoire.
\newpage
Concernant l'appartenance, le déplacement se comporte de deux façons :
\item Si la valeur est uniquement dans la \texttt{pile} : un \textit{deep copy} sera effectué, cela permet de copier l'ensemble de la structure dans la nouvelle variable.
\item Si la valeur utilise le \texttt{tas} : un \textit{shallow copy} sera effectué, la variable déplacée devient invalide.
\end{itemize}
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
// Deep copy : \textbf{var2} va copier la valeur de \textbf{var1}
let var1 = 12;
let var2 = var1;
println!("{}", var1); // "12"
}
\end{lstlisting}
\begin{lstlisting}[style=Rust, language=Rust]
fn main() {
// Shallow copy : \textbf{var2} va prendre l'appartenance sur \textbf{var1}
let var1 = String::from("Hello world");
let var2 = var1;
// \textbf{var1} est donc devenue invalide.
println!("{}", var1); // Erreur de compilation : Utilisation d'une valeur déplacée.
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
}
\end{lstlisting}
\section{Modularité}
La modularité d'un langage de programmation permet une hiérarchie séparée en unités logiques capable d'interagir entre-eux.
Un module est un ensemble de fonctions, structures, traits et même d'autres modules.
\begin{lstlisting}[style=Rust, language=Rust]
// Définition du module nommé \textbf{my\_mod}
mod my_mod {
// Cette fonction n'est disponible que dans cette scope.
fn private_function() {
println!("Call private_function()");
}
// Le mot clé \textbf{pub} permet de rendre disponible cette fonction.
pub fn public_function() {
println!("Call public_function()");
private_function();
}