diff --git a/essential_rust.tex b/essential_rust.tex index 7fa5c5e7721475ce96d2c715af3b7c5e8149d6fc..c3c4be0156451a1c93a41e64fca4b21bda75130d 100644 --- a/essential_rust.tex +++ b/essential_rust.tex @@ -244,18 +244,17 @@ fn add(x: i32, y: i32) -> i32 { \newpage -\subsection{Chaînes de caractères (introduction)} - -Pour une explication plus poussée des différents types de chaînes de caractères, -voir \Cref{advancedStrings}. +\subsection{Chaînes de caractères} -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 : +Les chaînes de caractères en Rust supportent \texttt{UTF-8}, et +prennent principalement un format parmi : \begin{itemize} - \item chaîne de caractères allouée dynamiquement (type \texttt{String}). - \item référence non modifiable, nommée slice (type \texttt{\&str}). + \item chaîne de caractères allouée dynamiquement (type \texttt{String}) + \item référence non modifiable, nommée slice (type \texttt{\&str}) + \item tableau d'octets, surtout utilisé pour la compatibilité avec C (type \texttt{\&[u8]}) \end{itemize} +On peut facilement convertir d'un type à un autre. \begin{lstlisting}[style=Rust, language=Rust] fn main() { // Une chaîne entre "" est une \&str @@ -276,15 +275,17 @@ fn main() { } \end{lstlisting} -Il y a des choses qu'on peut ou ne peut pas faire avec ces deux formats : +Il y a des choses qu'on peut ou ne peut pas faire avec ces 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 peut lire et modifier des octets individuels de \texttt{\&[u8]} + mais ce type ne gère pas l'UTF-8 et n'est pas affichable tel quel \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. +un type ou un autre, donc les conversions sont fréquentes. On verra souvent des erreurs de compilation comme celle-ci : @@ -662,13 +663,13 @@ fn main() { name: "Anthony".to_string(), nickname: Nickname::Some("Antho".to_string()), }; - + let user_two: User = User { id: 2, name: "Luc".to_string(), nickname: Nickname::None, }; - + print_user(user_one); // "1: Anthony aka Antho" print_user(user_two); // "2: Luc" } @@ -996,18 +997,18 @@ fn read_file() -> Result<String, io::Error> { Tous les programmes doivent gérer la façon dont ils utilisent la mémoire. \begin{itemize} - \item Certains langages comme Java ou Go ou OCaml, utilisent un ramasse-miette - détectant la mémoire qui n'est plus utilisée. - \item D'autres comme le C ou l'assembleur, requierent que le développeur - gère explicitement l'allocation et la désallocation de la mémoire. + \item Certains langages (comme Java ou Go ou OCaml) utilisent un ramasse-miette + détectant la mémoire qui n'est plus utilisée. + \item D'autres comme le C ou l'assembleur, requièrent que le développeur + gère explicitement l'allocation et la désallocation de la mémoire. \end{itemize} La première approche a l'inconvénient de causer des chutes de performance quand le ramasse-miettes s'exécute, et nécessite souvent que tous les objets se trouvent derrière une couche supplémentaire de pointeurs. - La seconde est source de bugs.\\ Rust utilise une autre approche : la mémoire est gérée par un système d'appartenance +(parfois appelé possession ou ownership) qui est vérifié à la compilation et qui permet de décider déterministiquement à quel moment chaque valeur est libérée.\\ @@ -1071,7 +1072,9 @@ fn main() { } \end{lstlisting} -La libération se fait grâce à l'appel de \texttt{drop} dans le trait \texttt{std::ops::Drop}.\\ +La libération se fait grâce à l'appel de \texttt{drop} dans le trait \texttt{std::ops::Drop}. +Cette fonction est appelée automatiquement par le compilateur si elle n'est pas déjà +invoquée explicitement.\\ Grâce aux règles de possession, cette fonction est souvent très facile à implémenter : @@ -1084,39 +1087,74 @@ La méthode \texttt{drop} prend possession de \texttt{self}... et ne fait rien d \texttt{self} sera libéré, puisque son nouveau possesseur cessera d'exister à la fin du corps de la fonction. - - \newpage -Concernant l'appartenance, le déplacement se comporte de deux façons : -\begin{itemize} - \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} +Lorsqu'on transfère la possession, il faut parfois déplacer des valeurs. Tout ce qui est stocké +sur la pile sera copié à sa nouvelle position. Ce qui est stocké sur le tas restera inchangé. \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; + // Ici le déplacement va engendrer une copie profonde de \textbf{var} + let var = [1, 2, 3, 4, 5]; // le tableau est stocké sur la pile + transfer(var); // on transfère la possession +} - println!("{}", var1); // "12" +fn transfer(v: [usize; 5]) { + // v a été déplacé vers sa nouvelle position sur la pile } \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. + // Cette fois la copie de \textbf{var} sera seulement en surface + let var = vec![1, 2, 3, 4, 5]; // le tableau est stocké sur le tas + transfer(var); // on transfère la possession +} - println!("{}", var1); // Erreur de compilation : Utilisation d'une valeur déplacée. +fn transfer(v: Vec<usize>) { + // une nouvelle valeur a été créée sur la pile, + // elle pointe vers la même addresse dans le tas qu'avant le transfert de possession } \end{lstlisting} +Parfois on veut vraiment dupliquer une valeur : on a besoin d'une copie +parce qu'on va modifier la valeur mais on veut se souvenir de l'original. + +Pour cela on dispose des traits \texttt{Clone} et \texttt{Copy}. +Si un objet implémente \texttt{Clone} on peut en créer une copie indépendante +avec \texttt{.clone()}. \texttt{Copy} fait la même chose mais de manière implicite. + +Pour ne pas avoir de mauvaise surprise de performance on n'implémente \texttt{Copy} +que pour les objets dont la copie ne coûte presque rien. +\begin{lstlisting}[style=Rust, language=Rust] +#[derive(Clone)] // demande au compilateur de générer automatiquement .clone() pour Gros +struct Gros([usize; 100]); + +#[derive(Clone, Copy)] // ici on se permet de rajouter Copy + // parce qu'on sait que dupliquer + // trois valeurs sera très rapide +struct Petit([usize; 3]); + +fn main() { + let gros1 = Gros([0; 100]); + let mut gros2 = gros1.clone(); // valeur dupliquée explicitement + gros2.0[0] = 1; + println!("{} != {}", gros1.0[0], gros2.0[0]); + // "0 != 1" + + let petit1 = Petit([0; 3]); + let mut petit2 = petit1; // valeur dupliquée implicitement + petit2.0[0] = 1; + println!("{} != {}", petit1.0[0], petit2.0[0]); + // "0 != 1" +} +\end{lstlisting} +Note: des fois un type n'implémente \textit{pas} \texttt{Clone} parce que pouvoir +en créer une copie serait dangereux. Il n'y a pas besoin de s'en soucier pour nos +types personnels : si \texttt{\#[derive(Clone)]} réussit c'est que la copie ne +pose pas de souci. + +% HERE \section{Modularité} @@ -1562,6 +1600,12 @@ struct User { \subsection{Tout est une expression} \label{expressionsEverywhere} +\subsection{Newtype} +\label{patternNewtype} + +\subsection{Builder} +\label{patternBuilder} + \subsection{Déstructuration} \label{advancedDestructuring} @@ -1574,6 +1618,9 @@ struct User { \subsection{Lifetimes} \label{advancedLifetimes} +\subsection{Traits} +\label{advancedTraits} + \subsection{Conteneurs} Les conteneurs sont des structures permettant de stocker des données.