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),
\end{lstlisting}
\newpage
L'exemple précédent fonctionne correctement, cependant, l'enum représentant la possibilité devrait être dupliquée afin d'être compatible avec d'autres types.
On peut utiliser les types génériques pour résoudre ce problème :
enum Maybe<T> { // L'enum \textbf{Maybe<T>} se compose :
Just(T), // - soit d'un élément, qui est de type T.
Nothing, // - soit d'aucun élément.
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
}
struct User {
id: i32,
name: String,
nickname: Maybe<String>, // Utilisation de l'enum Maybe avec comme type String
}
fn main() {
let user_one: User = User {
id: 1,
name: "Anthony".to_string(),
nickname: Maybe::Just("Antho".to_string()),
};
let user_two: User = User {
id: 2,
name: "Luc".to_string(),
nickname: Maybe::Nothing,
};
print_user(user_one); // "1: Anthony aka Antho"
print_user(user_two); // "2: Luc"
}
fn print_user(user: User) {
match user.nickname {
Maybe::Just(nickname) => println!("{}: {} aka {}", user.id, user.name, nickname),
Maybe::Nothing => println!("{}: {}", user.id, user.name),
}
}
\end{lstlisting}
\vspace{.5cm}
L'enum \texttt{Maybe<T>} est présente en Rust sous cette forme :
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
\begin{lstlisting}[style=Rust, language=Rust]
enum Option<T> {
Some(T),
None,
}
\end{lstlisting}
\section{Méthodes de structures}
Afin de contraindre l'accessibilité à notre donnée, il est possible d'utiliser des méthodes de structures.
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]
struct User {
id: i32,
name: String,
nickname: Option<String>,
}
// On va implémenter des fonctions pour la structure \textbf{User}.
impl User {
// La fonction \textbf{print} prend la structure en paramètre.
fn print(&self) {
match &self.nickname {
Option::Some(nickname) => println!("{}: {} aka {}", self.id, self.name, nickname),
Option::None => println!("{}: {}", 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,
};
user_one.print(); // "1: Anthony aka Antho"
user_two.print(); // "2: Luc"
}
\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}.
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
\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.
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
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();
}
// Définition du sous-module nommé \textbf{nested}
pub mod nested {
pub fn nested_function() {
println!("Call nested_function()");
}
}
pub fn public_call_nested() {
println!("Call public_call_nested()");
// Appel de la fonction de \textbf{nested} grâce à l'utilisation de \textbf{::}
nested::nested_function();
}
}
fn public_function() {
println!("Call public_function() that isn't in module");
}
fn main() {
public_function(); // "Call public\_function() that isn't in module"
my_mod::public_function(); // "Call public\_function()"
// "Call private\_function()"
my_mod::nested::nested_function(); // "Call nested\_function()"
my_mod::public_call_nested(); // "Call public\_call\_nested()"
// "Call nested\_function()"
}
\end{lstlisting}
Le mot clé \texttt{use}, vu dans les exemples précédents, permet de ne pas spécifier l'ensemble du chemin pour accéder à une fonction.
Il est possible de déplacer un module dans un fichier spécifique en respectant l'architecture de \texttt{cargo}.