Discussion du code gen_types_composes

Dans cette partie nous discutons de ce code.

Concepts

Les concepts abordés dans cet exemple sont:

  1. La généricité et les traits.
  2. Le trait Minimum.
  3. Les tuples
  4. Les traits Clone et Copy.

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, ...)

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