diff --git a/essential_rust.tex b/essential_rust.tex index 0821077822613dcd328e8285b15a9ef329066a1f..8a65c1f9d10f8d516a5186cbef60968b416991b7 100644 --- a/essential_rust.tex +++ b/essential_rust.tex @@ -244,7 +244,7 @@ fn add(x: i32, y: i32) -> i32 { \newpage -\subsection{Chaînes de caractères} +\section{Chaînes de caractères} Les chaînes de caractères en Rust supportent \texttt{UTF-8}, et prennent principalement un format parmi : @@ -446,6 +446,12 @@ fn somme(vect: &Vec<usize>) -> usize { } \end{lstlisting} +Maintenant que nous avons vu les références nous pouvons voir la forme réelle +de \texttt{\&[Type]} et \texttt{\&str}. En réalité ce sont des références vers +des objets de type \texttt{[Type]} et \texttt{str}, mais comme ces objets n'ont +pas de taille connue on ne peut jamais les stocker sur la pile, donc on y accède +toujours via une référence.\\ + 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. @@ -773,14 +779,15 @@ impl User { \section{Traits} -Les traits sont des ensembles de méthodes que les types sur lesquels ils sont appliqués -doivent implémenter. +Les traits sont des ensembles (éventuellement vides) de méthodes regroupées +sous un nom. Un type ``implémente un trait'' si il a une implémentation de toutes +les méthodes du trait. -Ils permettent de grouper des objets qui implémentent une fonctionnalité commune, -et certains donnent accès à des fonctionalités spéciales. +Les traits permettent de grouper des objets qui implémentent une fonctionnalité +commune, et certains donnent accès à des fonctionalités spéciales. Par exemple, pour utiliser \texttt{println!}, il faut que le type implémente -l'ensemble des méthodes du trait \texttt{std::fmt::Display} pour pouvoir afficher. +l'ensemble des méthodes du trait \texttt{std::fmt::Display}. Le trait \texttt{Display} ne contient qu'une seule méthode : \texttt{fmt}. @@ -829,6 +836,8 @@ Voici un aperçu de quelques uns des autres traits les plus fréquents : \newpage +\subsection{Généricité} + On peut utiliser des traits pour implémenter une seule fois une fonctionalité qui ne dépend que de quelques méthodes contenues dans des traits.\\ @@ -838,16 +847,14 @@ Cela s'exprime par \begin{lstlisting}[style=Rust, language=Rust] use std::fmt; -fn afficher_vec<T>(vec: Vec<T>) -where T: fmt::Display -{ +fn afficher_vec<T: fmt::Display>(vec: Vec<T>) { for elem in vec { println!("{elem}"); } } fn main() { - afficher_vec::<usize>(vec![1, 2, 3]); + afficher_vec::<usize>(vec![1, 2, 3]); // Note : ::<Type> n'est souvent pas nécessaire afficher_vec::<char>(vec!['a', 'b', 'c']); afficher_vec::<&str>(vec!["un", "deux", "trois"]); } @@ -881,6 +888,79 @@ fn main() { } \end{lstlisting} +\subsection{Traits paramétrés} + +On voit parfois des paramètres dans les noms de traits, comme par exemple +\texttt{Iterator<Item = u8>} ou bien \texttt{From<u8>}. + +Le premier indique que en plus des méthodes, le trait \texttt{Iterator} demande +la définition d'un type à l'intérieur, comme suit : + +\begin{lstlisting}[style=Rust, language=Rust] +impl Iterator for MonType { + type Item = u8; + ... +} +\end{lstlisting} +Le second signifie que l'implémentation peut dépendre du type d'un paramètre, +comme nous en verrons un exemple dans \Cref{implFrom}. + +\subsection{Implémentations automatiques} + +On peut aussi demander d'implémenter un trait automatiquement si un autre +trait est implémenté. + +Par exemple cet exemple tiré de la +\href{https://doc.rust-lang.org/src/alloc/string.rs.html#2390-2404}{librairie standard} +indique que le trait \texttt{ToString} est automatiquement implémenté pour tout type +qui implémente \texttt{Display}. + +\begin{lstlisting}[style=Rust, language=Rust] +impl<T: fmt::Display> ToString for T { + fn to_string(&self) -> String { + // ici l'implémentation par défaut + } +} +\end{lstlisting} + +Cette utilisation rend particulièrement intéressant le fait d'utiliser les +traits fournis par la librairie standard, car pour un trait qu'on définit +explicitement on gagne sans rien faire de plus accès à toutes les fonctionnalités +qui reposent sur ce trait déjà implémentées par d'autres personnes. + +C'est aussi à cela que servent les traits qui ne contiennent aucune méthode. +Des traits comme \texttt{Copy}, \texttt{Sync}, \texttt{Send}, \texttt{Sized} +(qui indiquent respectivement que l'objet est facilement duplicable, qu'il est +possible de l'utiliser depuis plusieurs threads en même temps, qu'il est possible +de le déplacer d'un thread à un autre, et que sa taille est connue) servent +de marqueurs et permettent de dire par exemple que notre fonction peut s'appliquer +à ``n'importe quel type dont on connaît la taille''. + +Par exemple voici un conteneur qui contient deux valeurs quelconques et on veut +exprimer comment en faire une copie. +\begin{lstlisting}[style=Rust, language=Rust] +struct Paire<U, V> { + u: U, + v: V, +} + +impl<U: Clone, V: Clone> Clone for Paire<U, V> { + fn clone(&self) -> Self { + Paire { + u: self.u.clone(), + v: self.v.clone(), + } + } +} +// Si on retire les ": Clone" on a une erreur "u et v n'ont pas de méthode Clone" +// Note: en pratique on n'écrira jamais nous-mêmes le code ci-dessus, car c'est +// exactement ce qui est généré automatiquement par +// \#[derive(Clone)] +// struct Paire<U, V> { ... } +\end{lstlisting} + + + \section{Gestion des erreurs} @@ -894,6 +974,8 @@ Rust a deux manières de gérer les erreurs à l'exécution : Il est bien sûr facile de convertir une erreur rattrapable en erreur non rattrapable, mais l'inverse est par définition impossible. +\subsection{\texttt{panic!}: erreur fatale} + \begin{lstlisting}[style=Rust, language=Rust] fn main() { let message = "AAAAAAAAAAAAAA"; @@ -927,6 +1009,9 @@ indice trop grand d'un tableau (implémenté par \texttt{index} de \newpage +\subsection{\texttt{Result} et \texttt{Option}: erreurs non fatales} +\label{errorHandling} + Pour les erreurs non-fatales, Rust a la structure de donnée suivante : \begin{lstlisting}[style=Rust, language=Rust] @@ -941,7 +1026,8 @@ Utilisons cette structure afin de faire un programme pour lire un fichier : \begin{lstlisting}[style=Rust, language=Rust] use std::fs::File; use std::io; -use std::io::Read; // Permet d'utiliser la fonction \textbf{read\_to\_string()} du trait Read +use std::io::Read; // Permet d'utiliser la fonction \textbf{read\_to\_string()} du trait Read + // (pour savoir pourquoi ce use est nécessaire, consulter \Cref{advancedTraits}) fn main() { match read_file() { @@ -992,7 +1078,199 @@ fn read_file() -> Result<String, io::Error> { } \end{lstlisting} +\newpage +\section{Conversions de type} +Les conversions de type en Rust peuvent paraître complexes car il en existe de nombreuses +variantes. + +\subsection{Primitives} +La conversion entre primitives se fait avec le mot-clé \texttt{as}. +À part quelques exceptions très restreintes que nous verrons juste après, +\textit{toutes} les conversions doivent être explicites, même d'un type entier +à un autre. +\begin{lstlisting}[style=Rust, language=Rust] +fn main() { + let i: i8 = 5; + let u: u8 = i as u8; // u vaut 5 + + // u + i -> erreur: ne peut pas additionner u8 + i8 + // Il faut faire + let i_plus: i8 = u as i8 + i; + let u_plus: u8 = u + i as u8; + + let num_a: u8 = 'a' as u8; // 97 + let chr_b: char = 98 as char; // 'b' + + let deux_f: f64 = 2 as f64; // 2.0 +} +\end{lstlisting} + +\subsection{Traits} + +Certains traits signalent la possibilité de faire une conversion vers ou depuis +un autre type. + +\subsubsection{Infaillibles} +\label{implFrom} +Les traits \texttt{std::convert::\{From, Into\}} décrivent des conversions +infaillibles. + +Il vaut mieux implémenter uniquement \texttt{From} car \texttt{Into} est +généré automatiquement en fonction de \texttt{From} si celui-ci existe. + + +\begin{lstlisting}[style=Rust, language=Rust] +struct Paire<U, V> { + u: U, + v: V, +} + +impl<U, V> std::convert::From<(U, V)> for Paire<U, V> { + fn from((u, v): (U, V)) -> Self { + Self { u, v } + } +} +impl<T> std::convert::From<[T; 2]> for Paire<T, T> { + fn from([u, v]: [T; 2]) -> Self { + Self { u, v } + } +} + +fn main() { + let p1: Paire<u8, char> = (1, 'a').into(); + let p2: Paire<f64, f64> = Paire::from([0.1, 0.2]); +} +\end{lstlisting} +Comme l'illustre cet exemple, on peut avoir des implémentations différentes +selon un paramètre de type. + + +\subsubsection{Faillibles} + +Toutes les conversions ne sont pas garanties de réussir. Si une conversion peut +échouer on utilise les traits \texttt{std::convert::\{TryFrom, TryInto\}} +qui sont les équivalents de \texttt{From} et \texttt{Into} avec en plus +une gestion des erreurs comme introduite dans \Cref{errorHandling}. + +\begin{lstlisting}[style=Rust, language=Rust] +struct U8(u8); +struct I64(i64); + +enum Taille { + TropPetit, + TropGros, +} + +impl std::convert::TryFrom<I64> for U8 { + type Error = Taille; + fn try_from(i: I64) -> Result<Self, Self::Error> { + if i.0 < 0 { + Err(Taille::TropPetit) + } else if i.0 > 255 { + Err(Taille::TropGros) + } else { + Ok(Self(i.0 as u8)) + } + } +} + +fn main() { + let i_ok: I64 = I64(25); + let i_ko: I64 = I64(-3); + let u_ok: Result<U8, Taille> = i_ok.try_into(); // Ok(U8(25)) + let u_ko: Result<U8, Taille> = i_ko.try_into(); // Err(Taille::TropPetit) +} +\end{lstlisting} + +\subsection{Implicites} + +Presque toutes les conversions sont explicites, mais il y a quelques exceptions +très particulières. Il n'est souvent pas nécessaire d'y réfléchir, et elles +sont très mineures car elles sont en réalité uniquement des simplifications de notations. + +\begin{enumerate} + \item perte de mutabilité :\\ + il y a une conversion automatique de \texttt{\&mut Type} vers \texttt{\&Type}. +\begin{lstlisting}[style=Rust, language=Rust] +fn main() { + let mut x: u8 = 0; + let mutable: &mut u8 = &mut x; + let immutable: &u8 = mutable; +} +\end{lstlisting} + On pourrait se passer de cette conversion car il est possible également d'écrire + + \texttt{let immutable: \&u8 = \&*mutable;} +\newpage + + \item slices :\\ + \texttt{\&Vec<Type>} et \texttt{\&[Type; Taille]} sont convertibles en \texttt{\&[Type]} +\begin{lstlisting}[style=Rust, language=Rust] +fn main() { + let vec: Vec<u8> = vec![0, 1, 2]; + let arr: [u8; 3] = [0, 1, 2]; + let vec_ref: &Vec<u8> = &vec; + let arr_ref: &[u8; 3] = &arr; + let slice_of_vec: &[u8] = vec_ref; + let slice_of_arr: &[u8] = arr_ref; +} +\end{lstlisting} + De la même manière \texttt{\&String} est convertible en \texttt{\&str}.\\ + Ces conversions sont une notation car elles permettent + juste d'écrire \texttt{\&val} au lieu de \texttt{\&val[..]}. + + \item auto-déréférencement :\\ + l'appel de méthodes peut parfois convertir automatiquement + de \texttt{self} vers \texttt{\&self} ou \texttt{\&mut self} + ainsi que réciproquement.\\ +\begin{lstlisting}[style=Rust, language=Rust] +#[derive(Clone, Copy)] // on reparlera de cette ligne plus tard +struct Vide(); +impl Vide { + fn avec_self(self) {} + fn avec_ref(&self) {} + fn avec_mut(&mut self) {} +} + +fn main() { + let mut v = Vide(); + // toutes les lignes qui suivent sont équivalentes alors que + // pour chaque groupe il n'y en a qu'une seule + // qui serait bien typée sans conversions automatiques + v.avec_self(); + v.avec_ref(); + v.avec_mut(); + (&v).avec_self(); + (&v).avec_ref(); + // (\&v).avec\_mut(); // seule combinaison interdite + (&mut v).avec_self(); + (&mut v).avec_ref(); + (&mut v).avec_mut(); + + // Ce qui suit est possible car Box est un type de + // pointeur qui implémente le trait std::ops::Deref + let mut b = Box::new(v); + b.avec_self(); + b.avec_ref(); + b.avec_mut(); // ne serait pas autorisé si b n'était pas mut +} +\end{lstlisting} + Cette fonctionalité permet d'écrire + \texttt{truc.bidule().machin().chose()} plutôt que\\ + \texttt{(*(\&(\&(*truc).bidule()).machin()).chose()}, le + second étant beaucoup moins lisible. + + Ce n'est en revanche pas la raison pour laquelle + \texttt{1 + 1} et \texttt{1 + (\&1)} sont toutes les deux + des expressions valides. Ici il y a vraiment deux implémentations + similaires mais distinctes de \texttt{std::ops::Add}: une pour + \texttt{impl Add<u8> for u8} et une pour \texttt{impl Add<\&u8> for u8}. + +\end{enumerate} + + +\newpage \section{Appartenance (\textit{Ownership})} \label{ownership} @@ -1715,6 +1993,10 @@ car ils ont souvent besoin d'avoir accès à des données qui ne sont pas \textt \begin{lstlisting}[style=Rust, language=Rust] +pub fn add(a: u8, b: u8) -> u8 { + a + b +} + #[cfg(test)] // Le bloc qui suit ne sera pas compilé si on n'est // pas en train de faire des tests mod test_add { @@ -1792,9 +2074,16 @@ On peut la lire avec \texttt{cargo doc --open} \section{Notions avancées} +Cette section contient des notions qui ne sont pas nécessaires à la +première lecture. + +Il est possible de la survoler pour voir ce qu'elle contient puis d'y revenir +quand on estime avoir besoin de plus d'informations sur un sujet spécifique. + \subsection{Tout est une expression} \label{expressionsEverywhere} + \subsection{Newtype} \label{patternNewtype}