Discussion du code gen_types_composes
Dans cette partie nous discutons de ce code.
Concepts
Les concepts abordés dans cet exemple sont:
Discussion
La généricité et les traits
En Rust (comme dans beaucoup de langages) on a un moyen d'éviter de dupliquer du code en utilisant le concept de généricité. Il s'agit ici de remplacer un type par un caractère générique lors de la définition d'un type ou d'une fonction.
Jusqu'ici nous avions une structure NumberOrNothing
qui contenait soit Nothing
soit Number(i32)
qui encapsule un entier 32 bits. Afin d'éviter de devoir réécrire tout le code pour chaque
type (u64
, double
, ou n'importe quel autre type) on va réécrire notre type énuméré
en le renommant astucieusement SomethingOrNothing
(en effet, il se pourrait que nous ne voulions
plus uniquement trouver le plus petit nombre dans une liste, mais on pourrait vouloir
trouver le mot le plus "petit" dans l'ordre lexicographique). Ainsi on a
#![allow(unused)] fn main() { enum SomethingOrNothing<T> { Nothing, Something(T), } }
Il faut noter ici la présence du caractère générique T
, qui est déclaré comme générique
lors de la définition du type SomethingOrNothing<T>
, puis est utilisé à la place de i32
dans Something(T)
.
Maintenant qu'on a changé la définition de noter type, plus rien fonctionne et on doit adapter
le reste du code. Pour aller dans l'ordre, on doit modifier l'implémentation de la fonction SomethingOrNothing::new(val)
#![allow(unused)] fn main() { enum SomethingOrNothing<T> { Nothing, Something(T), } impl<T> SomethingOrNothing<T> { fn new(val: T) -> SomethingOrNothing<T> { SomethingOrNothing::Something(val) } } }
On voit ici qu'il faut annoter tout le bloc impl<T> SomethingOrNothing<T>
avec le type générique
afin qu'il puisse être réutilisé dans les fonctions statiques. En effet,
si on omet les <T>
on une erreur de compilation
#![allow(unused)] fn main() { enum SomethingOrNothing<T> { Nothing, Something(T), } impl SomethingOrNothing<T> { fn new(val: T) -> SomethingOrNothing<T> { SomethingOrNothing::Something(val) } } }
Afin d'illustrer une particularité de la généricité de Rust, nous avons également réécrit la fonction
print(val)
#![allow(unused)] fn main() { enum SomethingOrNothing<T> { Nothing, Something(T), } // Print function // We know the generic type T must be Displayable fn print<T: std::fmt::Display>(val: SomethingOrNothing<T>) { match val { SomethingOrNothing::Nothing => println!("Nothing."), SomethingOrNothing::Something(val) => println!("Something is: {}", val), } } }
On voit ici qu'il y a une annotation particulière dans l'entête de la fonction, T: std::fmt::Display
fn print<T: std::fmt::Display>(val: SomethingOrNothing<T>)
Cette syntaxe dit au compilateur que le type générique implémente une fonctionnalité
particulière, nommée trait, qui dit au programme comment afficher une variable du type
générique T
. On voit qu'on doit afficher la valeur val
encapsulée dans Something(val)
SomethingOrNothing::Something(val) => println!("Something is: {}", val),
Hors si on ne dit pas à notre programme comment faire cet affichage, il sera bien embêté. Ici,
nous devons donc préciser qu'il est nécessaire que T
implémente le trait Display
sinon le programme ne compilera pas (cliquez sur play pour le vérifier)
#![allow(unused)] fn main() { enum SomethingOrNothing<T> { Nothing, Something(T), } fn print<T>(val: SomethingOrNothing<T>) { match val { SomethingOrNothing::Nothing => println!("Nothing."), SomethingOrNothing::Something(val) => println!("Something is: {}", val), } } }
On retrouve la contrainte d'implémenter le trait Display
dans la fonction print_tab(tab)
.
Corriger le code ci-dessous pour qu'il compile
const SIZE: usize = 9; fn read_command_line() -> [i32; SIZE] { [10, 32, 12, 43, 52, 53, 83, 2, 9] } fn print_tab<T>(tab: [T; SIZE]) { for t in tab { print!("{} ", t); } println!(); } fn main() { print_tab(read_command_line()); }
Le trait Minimum
La fonctionnalité principale dont nous avons besoin pour que notre code fonctionne est de pouvoir trouver
le minimum d'une liste de SomethingOrNothing<T>
(voir l'appel à la fonction current_minimum.min(SomethingOrNothing::new(t))
).
fn find_min<T: Minimum>(tab: [T; SIZE]) -> SomethingOrNothing<T> {
let mut current_minimum = SomethingOrNothing::Nothing;
// Here is T is not Copyable tab is consumed and cannot be returned
for t in tab {
current_minimum = current_minimum.min(SomethingOrNothing::new(t));
}
current_minimum
}
Ainsi on doit annoter T
pour qu'il puisse calculer la plus petite valeur entre deux SomethingOrNothing<T>
. On va donc devoir écrire définir notre premier trait.
On définit un trait à l'aide de la syntaxe suivante
#![allow(unused)] fn main() { trait Minimum: Copy { fn min(self, rhs: Self) -> Self; } }
Ici le trait Minimum
sera implémenté sur un type qui implémente le trait Copy
(un trait qui
garantit qu'on sait comment copier une valeur). Notre trait n'a que la fonction min
(le nombre
de fonction dans un trait est arbitraire, il peut même être nul).
L'entête de la fonction nous dit que la fonction min
a deux argument, la variable sur laquelle
elle est appelée, et une variable du même type (annoté avec le type Self
, avec un "s"
majuscule, contrairement à self
qui fait référence à une variable) et retourne également une
valeur du même type.
Il est important de vous rappeler qu'ici on ne sait pas encore quel est le type sur lequel on implémente cette fonction.
L'implémentation de Minimum
pour SomethingOrNothing<T>
se fait comme suit
trait Minimum: Copy {
fn min(self, rhs: Self) -> Self;
}
impl<T: Minimum> Minimum for SomethingOrNothing<T> {
fn min(self, rhs: Self) -> Self {
match (self, rhs) {
(SomethingOrNothing::Nothing, SomethingOrNothing::Nothing) => {
SomethingOrNothing::Nothing
}
(SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => {
SomethingOrNothing::new(lhs.min(rhs))
}
(SomethingOrNothing::Nothing, SomethingOrNothing::Something(rhs)) => {
SomethingOrNothing::new(rhs)
}
(SomethingOrNothing::Something(lhs), SomethingOrNothing::Nothing) => {
SomethingOrNothing::new(lhs)
}
}
}
}
Pour implémenter un trait sur un type on utilise la syntaxe
impl Trait for Type
puis implémenter toutes les fonctions se trouvant dans la définition du trait.
Ici, nous n'avons que la fonction min
à implémenter.
Le type SomethingOrNothing
est générique, donc il faut à nouveau notre code
pour en tenir compte. On voit que le type générique doit lui-même
implémenter le trait Minimum
pour que cette fonction compile: on voit l'utilisation
de la fonction min
SomethingOrNothing::new(lhs.min(rhs))
Nous verrons le détail de la syntaxe de cette fonction dans la section sur les tuples.
Comme nous utilisons des SomethingOrNothing<i32>
dans ce code, nous devons implémenter
le trait Minimum
pour des entiers. Ce qui est fait dans le bout de code suivant
#![allow(unused)] fn main() { trait Minimum: Copy { fn min(self, rhs: Self) -> Self; } impl Minimum for i32 { fn min(self, rhs: Self) -> Self { if self < rhs { self } else { rhs } } } }
Les tuples
On découvre ici, une syntaxe inconnue: les tuples. Un tuple est une collection d'un nombre arbitraire de valeurs dont les types peuvent être différents. Un tuple lui-même est une valeur dont le type est
(T1, T2, T3, ...)
où T1
, T2
, T3
, ... sont les types des membres du tuple. Un tuple peut être utilisé pour retourner plusieurs valeurs depuis une fonction par exemple.
Ainsi quand on fait du pattern mathching
match (self, rhs)
on va créer un tuple avec les valeurs de self
et de rhs
et vérifier les types de toutes les valeurs possibles pour ces types énumérées. On a donc 4 cas différents:
match (self, rhs) {
(SomethingOrNothing::Nothing, SomethingOrNothing::Nothing) => {
SomethingOrNothing::Nothing
}
(SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => {
SomethingOrNothing::new(lhs.min(rhs))
}
(SomethingOrNothing::Nothing, SomethingOrNothing::Something(rhs)) => {
SomethingOrNothing::new(rhs)
}
(SomethingOrNothing::Something(lhs), SomethingOrNothing::Nothing) => {
SomethingOrNothing::new(lhs)
}
}
quand on a deux variantes Nothing
, on retourne Nothing
, quand on a une variante Nothing
et une valeur Something(val)
on retourne Something(val)
et finalement quand on a
Something(lhs)
et un Something(rhs)
, c'est Something(lhs.min(rhs))
qui est retourné (donc
la valeur la plus petite qui encapsulée). D'où la nécessité que T
implémente le trait Minimum
.
Il faut noter à nouveau qu'il n'y a pas de ;
dans les blocs des variantes du pattern matching et que les valeurs sont retournées (la structure de contrôle match
est une expression).
Les traits Clone
et Copy
En Rust, il existe deux traits essentiels Copy
et Clone
. Le trait Copy
permet de copier les instances d'un type bit à bit. La copie est une action implicite. Par exemple, dans le code ci-dessous
let y : i32 = 5;
let x = y;
la valeur de y
(ici 5
) est copiée dans une zone mémoire nouvellement allouée qui ensuite est liée à la variable x
.
Dans notre code nous décidons d'autoriser la copie de notre type énuméré en implémentant le trait Copy
/// In gen_types_composes we introduce genericity through traits and in particular, [Copy], /// [Clone], [std::fmt::Display] . enum SomethingOrNothing<T> { Nothing, Something(T), } impl<T> SomethingOrNothing<T> { fn new(val: T) -> SomethingOrNothing<T> { SomethingOrNothing::Something(val) } } // Print function // We know the generic type T must be Displayable fn print<T: std::fmt::Display>(val: SomethingOrNothing<T>) { match val { SomethingOrNothing::Nothing => println!("Nothing."), SomethingOrNothing::Something(val) => println!("Something is: {}", val), } } impl<T: Clone> Clone for SomethingOrNothing<T> { fn clone(&self) -> Self { match self { SomethingOrNothing::Nothing => SomethingOrNothing::Nothing, SomethingOrNothing::Something(val) => SomethingOrNothing::new(val.clone()), } } } impl<T: Copy> Copy for SomethingOrNothing<T> {} // If we remove Copy, we have a problem with the t in tab // in the computation of the minimum. trait Minimum: Copy { fn min(self, rhs: Self) -> Self; } impl<T: Minimum> Minimum for SomethingOrNothing<T> { fn min(self, rhs: Self) -> Self { match (self, rhs) { (SomethingOrNothing::Nothing, SomethingOrNothing::Nothing) => { SomethingOrNothing::Nothing } (SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => { SomethingOrNothing::new(lhs.min(rhs)) } (SomethingOrNothing::Nothing, SomethingOrNothing::Something(rhs)) => { SomethingOrNothing::new(rhs) } (SomethingOrNothing::Something(lhs), SomethingOrNothing::Nothing) => { SomethingOrNothing::new(lhs) } } } } // i32 is Copyable as a very basic type as f32, f64, etc. // Arrays for example are not copyable. impl Minimum for i32 { fn min(self, rhs: Self) -> Self { if self < rhs { self } else { rhs } } } const SIZE: usize = 9; fn read_command_line() -> [i32; SIZE] { [10, 32, 12, 43, 52, 53, 83, 2, 9] } // Prints tab and returns tab. // Tab would be destructed at the end of the function otherwise. fn print_tab<T: std::fmt::Display>(tab: [T; SIZE]) { for t in tab { print!("{} ", t); } println!(); } fn find_min<T: Minimum>(tab: [T; SIZE]) -> SomethingOrNothing<T> { let mut current_minimum = SomethingOrNothing::Nothing; // Here is T is not Copyable tab is consumed and cannot be returned for t in tab { current_minimum = current_minimum.min(SomethingOrNothing::new(t)); } current_minimum } fn main() { let tab = read_command_line(); println!("Among the Somethings in the list:"); print_tab(tab); let min = find_min(tab); print(min); }
Comme on peut le voir ici, il n'y a pas de fonction à implémenter avec Copy
, ce trait permet uniquement d'effectuer une copie binaire des données.
Il est également important de noter qu'afin que notre type SomethingOrNothing<T>
implémente le trait Copy
, il est nécessaire que le type paramètrique T
implémente lui aussi Copy
. Ce qui veut dire plus simplement qu'un type complexe ne peut pas implémenter le trait Copy
si les types qu'il contient ne sont pas copiable.
On peut aussi remarquer qu'il est possible d'indiquer la nécessité que les types implémentant un trait soit copiables. Par exemple
/// In gen_types_composes we introduce genericity through traits and in particular, [Copy], /// [Clone], [std::fmt::Display] . enum SomethingOrNothing<T> { Nothing, Something(T), } impl<T> SomethingOrNothing<T> { fn new(val: T) -> SomethingOrNothing<T> { SomethingOrNothing::Something(val) } } // Print function // We know the generic type T must be Displayable fn print<T: std::fmt::Display>(val: SomethingOrNothing<T>) { match val { SomethingOrNothing::Nothing => println!("Nothing."), SomethingOrNothing::Something(val) => println!("Something is: {}", val), } } impl<T: Clone> Clone for SomethingOrNothing<T> { fn clone(&self) -> Self { match self { SomethingOrNothing::Nothing => SomethingOrNothing::Nothing, SomethingOrNothing::Something(val) => SomethingOrNothing::new(val.clone()), } } } impl<T: Copy> Copy for SomethingOrNothing<T> {} // If we remove Copy, we have a problem with the t in tab // in the computation of the minimum. trait Minimum: Copy { fn min(self, rhs: Self) -> Self; } impl<T: Minimum> Minimum for SomethingOrNothing<T> { fn min(self, rhs: Self) -> Self { match (self, rhs) { (SomethingOrNothing::Nothing, SomethingOrNothing::Nothing) => { SomethingOrNothing::Nothing } (SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => { SomethingOrNothing::new(lhs.min(rhs)) } (SomethingOrNothing::Nothing, SomethingOrNothing::Something(rhs)) => { SomethingOrNothing::new(rhs) } (SomethingOrNothing::Something(lhs), SomethingOrNothing::Nothing) => { SomethingOrNothing::new(lhs) } } } } // i32 is Copyable as a very basic type as f32, f64, etc. // Arrays for example are not copyable. impl Minimum for i32 { fn min(self, rhs: Self) -> Self { if self < rhs { self } else { rhs } } } const SIZE: usize = 9; fn read_command_line() -> [i32; SIZE] { [10, 32, 12, 43, 52, 53, 83, 2, 9] } // Prints tab and returns tab. // Tab would be destructed at the end of the function otherwise. fn print_tab<T: std::fmt::Display>(tab: [T; SIZE]) { for t in tab { print!("{} ", t); } println!(); } fn find_min<T: Minimum>(tab: [T; SIZE]) -> SomethingOrNothing<T> { let mut current_minimum = SomethingOrNothing::Nothing; // Here is T is not Copyable tab is consumed and cannot be returned for t in tab { current_minimum = current_minimum.min(SomethingOrNothing::new(t)); } current_minimum } fn main() { let tab = read_command_line(); println!("Among the Somethings in the list:"); print_tab(tab); let min = find_min(tab); print(min); }
où la notation trait Minimum: Copy
spécifie que Copy
doit être implémenté quand on implémente Minimum
.
Ici tous les types qui implémentent le trait Minimum
doivent également implémenter Copy
. C'est le cas par exemple du type i32
/// In gen_types_composes we introduce genericity through traits and in particular, [Copy], /// [Clone], [std::fmt::Display] . enum SomethingOrNothing<T> { Nothing, Something(T), } impl<T> SomethingOrNothing<T> { fn new(val: T) -> SomethingOrNothing<T> { SomethingOrNothing::Something(val) } } // Print function // We know the generic type T must be Displayable fn print<T: std::fmt::Display>(val: SomethingOrNothing<T>) { match val { SomethingOrNothing::Nothing => println!("Nothing."), SomethingOrNothing::Something(val) => println!("Something is: {}", val), } } impl<T: Clone> Clone for SomethingOrNothing<T> { fn clone(&self) -> Self { match self { SomethingOrNothing::Nothing => SomethingOrNothing::Nothing, SomethingOrNothing::Something(val) => SomethingOrNothing::new(val.clone()), } } } impl<T: Copy> Copy for SomethingOrNothing<T> {} // If we remove Copy, we have a problem with the t in tab // in the computation of the minimum. trait Minimum: Copy { fn min(self, rhs: Self) -> Self; } impl<T: Minimum> Minimum for SomethingOrNothing<T> { fn min(self, rhs: Self) -> Self { match (self, rhs) { (SomethingOrNothing::Nothing, SomethingOrNothing::Nothing) => { SomethingOrNothing::Nothing } (SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => { SomethingOrNothing::new(lhs.min(rhs)) } (SomethingOrNothing::Nothing, SomethingOrNothing::Something(rhs)) => { SomethingOrNothing::new(rhs) } (SomethingOrNothing::Something(lhs), SomethingOrNothing::Nothing) => { SomethingOrNothing::new(lhs) } } } } // i32 is Copyable as a very basic type as f32, f64, etc. // Arrays for example are not copyable. impl Minimum for i32 { fn min(self, rhs: Self) -> Self { if self < rhs { self } else { rhs } } } const SIZE: usize = 9; fn read_command_line() -> [i32; SIZE] { [10, 32, 12, 43, 52, 53, 83, 2, 9] } // Prints tab and returns tab. // Tab would be destructed at the end of the function otherwise. fn print_tab<T: std::fmt::Display>(tab: [T; SIZE]) { for t in tab { print!("{} ", t); } println!(); } fn find_min<T: Minimum>(tab: [T; SIZE]) -> SomethingOrNothing<T> { let mut current_minimum = SomethingOrNothing::Nothing; // Here is T is not Copyable tab is consumed and cannot be returned for t in tab { current_minimum = current_minimum.min(SomethingOrNothing::new(t)); } current_minimum } fn main() { let tab = read_command_line(); println!("Among the Somethings in the list:"); print_tab(tab); let min = find_min(tab); print(min); }
Le deuxième trait que nous retrouvons dans ce code est le trait Clone
. Clone
est un supertrait de Copy
, ce qui signifie qu'un type qui implémente Copy
doit nécessairement implémenter Clone
.
Le trait Clone
permet de dupliquer explicitement une instance. En effet, pour cloner une instance, il faut appeler la méthode clone()
explicitement
/// In gen_types_composes we introduce genericity through traits and in particular, [Copy], /// [Clone], [std::fmt::Display] . enum SomethingOrNothing<T> { Nothing, Something(T), } impl<T> SomethingOrNothing<T> { fn new(val: T) -> SomethingOrNothing<T> { SomethingOrNothing::Something(val) } } // Print function // We know the generic type T must be Displayable fn print<T: std::fmt::Display>(val: SomethingOrNothing<T>) { match val { SomethingOrNothing::Nothing => println!("Nothing."), SomethingOrNothing::Something(val) => println!("Something is: {}", val), } } impl<T: Clone> Clone for SomethingOrNothing<T> { fn clone(&self) -> Self { match self { SomethingOrNothing::Nothing => SomethingOrNothing::Nothing, SomethingOrNothing::Something(val) => SomethingOrNothing::new(val.clone()), } } } impl<T: Copy> Copy for SomethingOrNothing<T> {} // If we remove Copy, we have a problem with the t in tab // in the computation of the minimum. trait Minimum: Copy { fn min(self, rhs: Self) -> Self; } impl<T: Minimum> Minimum for SomethingOrNothing<T> { fn min(self, rhs: Self) -> Self { match (self, rhs) { (SomethingOrNothing::Nothing, SomethingOrNothing::Nothing) => { SomethingOrNothing::Nothing } (SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => { SomethingOrNothing::new(lhs.min(rhs)) } (SomethingOrNothing::Nothing, SomethingOrNothing::Something(rhs)) => { SomethingOrNothing::new(rhs) } (SomethingOrNothing::Something(lhs), SomethingOrNothing::Nothing) => { SomethingOrNothing::new(lhs) } } } } // i32 is Copyable as a very basic type as f32, f64, etc. // Arrays for example are not copyable. impl Minimum for i32 { fn min(self, rhs: Self) -> Self { if self < rhs { self } else { rhs } } } const SIZE: usize = 9; fn read_command_line() -> [i32; SIZE] { [10, 32, 12, 43, 52, 53, 83, 2, 9] } // Prints tab and returns tab. // Tab would be destructed at the end of the function otherwise. fn print_tab<T: std::fmt::Display>(tab: [T; SIZE]) { for t in tab { print!("{} ", t); } println!(); } fn find_min<T: Minimum>(tab: [T; SIZE]) -> SomethingOrNothing<T> { let mut current_minimum = SomethingOrNothing::Nothing; // Here is T is not Copyable tab is consumed and cannot be returned for t in tab { current_minimum = current_minimum.min(SomethingOrNothing::new(t)); } current_minimum } fn main() { let tab = read_command_line(); println!("Among the Somethings in the list:"); print_tab(tab); let min = find_min(tab); print(min); }
Comme on peut le voir dans le code ci-dessus, il est possible de définir un comportement arbitraire en redéfinissant la méthode clone()
.
En effet, le trait Clone
nous permet de définir librement le comportement attendu lors d'un clonage, ce qui n'est pas le cas avec Copy
.
Cette liberté a un coût, puisque que l'on peut écrire notre propre fonction de clonnage, ce dernier peut facilement devenir beaucoup plus coûteux que la simple copie binaire des données.
Le langage Rust offre un attribut afin de pouvoir implémenter simplement les traits Copy
et Clone
. Il s'agit de #[derive(...)]
.
Par exemple avec le code suivant
#![allow(unused)] fn main() { #[derive(Clone,Copy)] struct MyStruct; }
l'annotation permet d'indiquer au compilateur que notre struct MyStruct
nécessite une implémentation par défaut des traits Copy
et Clone
.
Ce qui est équivaut à écrire
#![allow(unused)] fn main() { struct MyStruct; impl Copy for MyStruct { } impl Clone for MyStruct { fn clone(&self) -> MyStruct { *self } } }
Le code
/// In gen_types_composes we introduce genericity through traits and in particular, [Copy], /// [Clone], [std::fmt::Display] . enum SomethingOrNothing<T> { Nothing, Something(T), } impl<T> SomethingOrNothing<T> { fn new(val: T) -> SomethingOrNothing<T> { SomethingOrNothing::Something(val) } } // Print function // We know the generic type T must be Displayable fn print<T: std::fmt::Display>(val: SomethingOrNothing<T>) { match val { SomethingOrNothing::Nothing => println!("Nothing."), SomethingOrNothing::Something(val) => println!("Something is: {}", val), } } impl<T: Clone> Clone for SomethingOrNothing<T> { fn clone(&self) -> Self { match self { SomethingOrNothing::Nothing => SomethingOrNothing::Nothing, SomethingOrNothing::Something(val) => SomethingOrNothing::new(val.clone()), } } } impl<T: Copy> Copy for SomethingOrNothing<T> {} // If we remove Copy, we have a problem with the t in tab // in the computation of the minimum. trait Minimum: Copy { fn min(self, rhs: Self) -> Self; } impl<T: Minimum> Minimum for SomethingOrNothing<T> { fn min(self, rhs: Self) -> Self { match (self, rhs) { (SomethingOrNothing::Nothing, SomethingOrNothing::Nothing) => { SomethingOrNothing::Nothing } (SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => { SomethingOrNothing::new(lhs.min(rhs)) } (SomethingOrNothing::Nothing, SomethingOrNothing::Something(rhs)) => { SomethingOrNothing::new(rhs) } (SomethingOrNothing::Something(lhs), SomethingOrNothing::Nothing) => { SomethingOrNothing::new(lhs) } } } } // i32 is Copyable as a very basic type as f32, f64, etc. // Arrays for example are not copyable. impl Minimum for i32 { fn min(self, rhs: Self) -> Self { if self < rhs { self } else { rhs } } } const SIZE: usize = 9; fn read_command_line() -> [i32; SIZE] { [10, 32, 12, 43, 52, 53, 83, 2, 9] } // Prints tab and returns tab. // Tab would be destructed at the end of the function otherwise. fn print_tab<T: std::fmt::Display>(tab: [T; SIZE]) { for t in tab { print!("{} ", t); } println!(); } fn find_min<T: Minimum>(tab: [T; SIZE]) -> SomethingOrNothing<T> { let mut current_minimum = SomethingOrNothing::Nothing; // Here is T is not Copyable tab is consumed and cannot be returned for t in tab { current_minimum = current_minimum.min(SomethingOrNothing::new(t)); } current_minimum } fn main() { let tab = read_command_line(); println!("Among the Somethings in the list:"); print_tab(tab); let min = find_min(tab); print(min); }
Rustlings
Les rustlings à faire dans ce chapitre sont les suivants:
Les traits
$ rustlings run traits1
$ rustlings run traits2
$ rustlings run traits3
$ rustlings run traits4
$ rustlings run traits5
Les tuples
$ rustlings run primitive_types5
$ rustlings run primitive_types6