IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Rust par l'exemple


précédentsommairesuivant

1. Hello World

Voici le code source d'un traditionnel « Hello World ».

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
// Ceci est un commentaire, et sera ignoré par le compilateur.

// Ceci est la fonction principale
fn main() {
// Toutes les déclarations se trouvant dans le corps de la fonction 
// seront exécutées lorsque le binaire est exécuté.
// Afficher du texte dans la console.
    println!("Hello World!");
}

println! est une macro qui affiche du texte sur la console.

Un binaire peut être généré en utilisant le compilateur Rust : rustc.

 
Sélectionnez
$ rustc hello.rs

rustc va produire un binaire nommé « hello » qui pourra être exécuté :

 
Sélectionnez
$ ./hello
Hello World!

Activité

Cliquez sur le bouton « Run » en début de section pour visualiser le résultat présenté. Ensuite, ajoutez une nouvelle ligne qui permettra de visualiser le résultat ci-dessous :

 
Sélectionnez
Hello World!
I'm a Rustacean!

1-1. Les commentaires

N'importe quel programme a besoin de commentaires, c'est pour cela que Rust supporte différentes syntaxes :

Les commentaires basiques ignorés par le compilateur :

  • // Les commentaires mono-lignes. ;
  • /* Les blocs de commentaires régis par leurs délimiteurs. */.

Les commentaires dédiés à la documentation qui seront convertis au format HTML :

  • /// Génère de la documentation pour ce qui suit ce commentaire. ;
  • //! Génère la documentation pour un conteneur (e.g. un module) ;
  • /*! Permet de rédiger un bloc entier de documentation.*/.

1-2. Affichage formaté

L'affichage est pris en charge par une série de macros déclarées dans le module std::fmt qui inclut :

  • format! : Construit la chaîne de caractères du texte à afficher ;
  • print! : Fait exactement la même chose que format!, mais le texte est affiché dans la console ;
  • println! : Fait exactement la même chose que print!, mais un retour à la ligne est ajouté.

Toutes formatent le texte de la même manière.

Note : la validité du formatage (i.e. Si la chaîne de caractères que vous soumettez peut être formatée comme vous le désirez) est vérifiée au moment de la compilation.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
fn main(){
    // En général, le marqueur '{}' sera automatiquement remplacé par 
    // n'importe quel argument. Il sera transformé en chaîne de caractères.
    println!("{} jours", 31);
    
    // Sans suffixe, 31 est de type i32. Vous pouvez changer le type de 31 avec
    // un suffixe. (e.g. 31i64)
    
    // Différents modèles peuvent être utilisés. 
    // Les marqueurs de position peuvent être utilisés.
    println!("{0}, voici {1}. {1}, voici {0}", "Alice", "Bob");
    
    // Les marqueurs peuvent également être
    // nommés
    println!("{sujet} {verbe} {objet}", 
    objet="le chien paresseux",
    sujet="Rapide, le renard",
    verbe="saute par-dessus");
    
    // Un formatage spécial peut être spécifié après un ':'.
    println!("{} personne sur {:b} sait lire le binaire, l'autre moité non.", 1, 2);
    
    // Vous pouvez aligner vers la droite votre texte en spécifiant 
    // la largeur (en espace) entre le côté gauche de la console 
    // et votre chaîne. Cet exemple affichera: "     1", un "1" après 5 espaces.

    println!("{number:>width$}", number=1, width=6);
    
    // Vous pouvez également remplacer les white spaces par des '0'.
    // Affiche: "000001"
    
    println!("{number:>0width$}", number=1, width=6);
    
    // Le nombre d'arguments utilisé est vérifié par le compilateur.
    // println!("Mon nom est {0}, {1} {0}", "Bond");
    // FIXME ^ Ajoutez l'argument manquant: "James".
    
    // On créé une structure nommé 'Structure' contenant un entier de type 'i32'.
    #[allow(dead_code)]
    struct Structure(i32);
    
    // Cependant, les types complexes tels que les structures demandent
    // une gestion de l'affichage plus complexe. Cela ne fonctionnera pas.
    // println!("Cette structure '{}' ne sera pas affichée...", Structure(3));
    // FIXME ^ Commentez/Décommentez cette ligne pour voir le message d'erreur.
}

std::fmt contient plusieurs traits qui structurent l'affichage du texte. Les deux plus « importants » sont listés ci-dessous :

  1. fmt::Debug : Utilise le marqueur {:?}. Applique un formatage dédié au débogage.
  2. fmt::Display : Utilise le marqueur {}. Formate le texte de manière plus élégante, plus « user friendly ».

Dans cet exemple, fmt::Display était utilisé parce que la bibliothèque standard fournit les implémentations pour ces types. Pour afficher du texte à partir de types complexes/personnalisés, d'autres étapes sont requises.

Activité

Réglez les deux problèmes dans le code ci-dessus (cf. FIXME) pour qu'il s'exécute sans erreurs.

Ajoutez une macro println! qui affiche : « Pi est, à peu près, égal à 3,142 » en contrôlant le nombre affiché de chiffres après la virgule. Dans le cadre de l'exercice, vous utiliserez let pi = 3.141592 comme estimation de Pi (Note : vous pourriez avoir besoin de consulter la documentation du module std::fmt pour configurer le nombre de décimaux à afficher).

Voir aussi

std::fmt, les macros, les structures, les traits.

1-2-1. Debug

Tous les types qui utilisent le formatage des traits du module std::fmt doivent en posséder une implémentation pour être affichés.

Les implémentations ne sont fournies automatiquement que pour les types supportés par la bibliothèque standard. Les autres devront l'implémenter « manuellement ».

Pour le trait fmt::Debug, rien de plus simple. Tous les types peuvent hériter de son implémentation (i.e. la créer automatiquement, sans intervention de votre part). Ce n'est, en revanche, pas le cas pour le second trait : fmt::Display.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
// Cette structure ne peut être affichée par `fmt::Debug`, 
// ni par `fmt::Display`.
struct UnPrintable(i32);

// L'attribut `derive` créé automatiquement l'implémentation requise 
// pour permettre à cette structure d'être affichée avec `fmt::Debug`.
#[derive(Debug)]
struct DebugPrintable(i32);

Également, tous les types de la bibliothèque standard peuvent être automatiquement affichés avec le marqueur {:?} :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
// On fait hériter l'implémentation de `fmt::Debug` pour `Structure`.
// `Structure` est une structure qui contient un simple entier de type `i32`.
#[derive(Debug)]
struct Structure(i32);

// On créé une structure nommée `Deep`, que l'on rend également affichable,
// contenant un champ de type `Structure`,
#[derive(Debug)]
struct Deep(Structure);

fn main() {
    // L'affichage avec le marqueur `{:?}` est similaire à `{}`,
    // pour des types standards comme les entiers et les chaînes de caractères.
    println!("{:?} mois dans une année.", 12);
    println!("{1:?} {0:?} est le nom de {actor:?}.",
             "Slater",
             "Christian",
             actor="l'acteur");

    // `Structure` peut être affichée !
    println!("{:?} peut désormais être affichée!", Structure(3));

    // Le problème avec `derive` est que vous n'avez aucun contrôle quant au résultat
    // affiché. Comment faire si je souhaite seulement afficher `7` ?
    println!("{:?} peut désormais être affichée!", Deep(Structure(7)));
}

Finalement, fmt::Debug permet de rendre un type personnalisé affichable en sacrifiant quelque peu « l'élégance » du résultat. Pour soigner cela, il faudra implémenter soit-même les services du traits fmt::Display.

Voir aussi

Les attributs, derive, std::fmt, les structures.

1-2-2. Display

fmt::Debug propose un formatage rudimentaire, et il peut être de bon ton de soigner ce que nous affichons. Pour ce faire, il faudra implémenter fmt::Display (qui utilise le marqueur {}).

Voici un exemple d'implémentation du trait :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
// On importe (via `use`) le module `fmt` pour le rendre accessible.
use std::fmt;

// Nous définissons une structure dans laquelle le trait `fmt::Display` 
// sera implémenté. Ce n'est qu'un simple tuple, nommée `Structure`, contenant un entier de type i32. 
struct Structure(i32);

// Pour pouvoir utiliser le marqueur `{}`, le trait `fmt::Display` doit être implémenté 
// manuellement pour le type.
impl fmt::Display for Structure {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // écrit le premier élément de la structure dans le flux en sortie
        // soumis: `f`. Renvoie une instance de `fmt::Result` qui témoigne du succès 
        // ou de l'échec de l'opération. Notez que `write!` possède une syntaxe très 
        // similaire à `println!`.
        write!(f, "{}", self.0)
    }
}

fmt::Display pourrait être plus lisible que fmt::Debug mais il présente un problème pour la bibliothèque standard. Comment les types ambiguës devraient être affichés ? Par exemple, si la bibliothèque standard devait implémenter un seul formatage pour toutes les « variantes » de Vec<T>, quel style devrait être choisi ? N'importe lequel ?

  1. Vec<Path> : /:/etc:/home/username:/bin (séparé par des « : ») ;
  2. Vec<i32> : 1,2,3 (séparé par des « , »).

Bien sûr que non, puisqu'il n'y a pas de mise en forme idéale pour tous les types et la bibliothèque standard n'en impose pas.

fmt::Display n'est pas implémenté pour la structure Vec<T> ni pour aucun autre conteneur générique. fmt::Debug  doit alors être utilisé pour ces ressources.

Ce n'est en revanche pas un problème pour les conteneurs(e.g. structures) qui ne sont pas génériques, fmt::Display peut être implémenté et utilisé.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
use std::fmt; // On importe le module `fmt`

// Une structure qui contient deux nombres. `Debug` va être hérité pour que les résultats
// puissent être comparés avec `Display`.
#[derive(Debug)]
struct MinMax(i64, i64);

// Implémentation du trait `Display` pour la structure `MinMax`.
impl fmt::Display for MinMax {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // On utilise `self.nombre` pour faire référence à la donnée se trouvant
        // à cette position.
        write!(f, "({}, {})", self.0, self.1)
    }
}

// Définissons une structure où les champs sont nommés pour comparer.
#[derive(Debug)]
struct Point2D {
    x: f64,
    y: f64,
}

// On implémente également le trait fmt::Display pour la structure Point2D
impl fmt::Display for Point2D {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // On désigne les champs de notre choix. (en l'occurrence `x` et `y`)
        write!(f, "x: {}, y: {}", self.x, self.y)
    }
}

fn main() {
    let minmax = MinMax(0, 14);

    println!("Comparaison des structures:");
    println!("Display: {}", minmax);
    println!("Debug: {:?}", minmax);

    let big_range =   MinMax(-300, 300);
    let small_range = MinMax(-3, 3);

    println!("Le grand intervalle est {big} et le petit est {small}",
             small = small_range,
             big = big_range);

    let point = Point2D { x: 3.3, y: 7.2 };

    println!("Comparaison des points:");
    println!("Display: {}", point);
    println!("Debug: {:?}", point);
    
    // Erreur. Les traits `Debug` and `Display` étaient implémentés mais 
    // le marqueur `{:b}` requiert l'implémentation du trait `fmt::Binary`.
    // Cela ne fonctionnera pas.
    // println!("A quoi ressemble Point2D formaté en binaire: {:b} ?", point);
}

Donc fmt::Display a été implémenté mais ce n'est pas le cas de fmt::Binary, il ne peut alors pas être utilisé.

std::fmt possède de nombreux traits et chacun doit posséder sa propre implémentation. Pour plus d'informations, nous vous invitons à consulter la documentation du module.

Activité

Après avoir constaté le résultat de l'exemple ci-dessus, aidez-vous de la structure Point2D pour ajouter à l'exemple une nouvelle structure nommée Complex. Voici le résultat attendu lorsqu'une instance de la structure Complex sera affichée :

 
Sélectionnez
Display: 3.3 + 7.2i
Debug: Complex { real: 3.3, imag: 7.2 }

Voir aussi

L'attribut derive, std::fmt, les macros, les structures, les traits, le mot-clé use.

1-2-2-1. Exemple d'utilisation : La structure List

Implémenter le traitfmt::Display pour une structure où les éléments doivent être gérés séquentiellement est assez délicat. Le problème réside dans le fait que chaque appel de la macro write! génère une instance de fmt::Result. Une bonne gestion de ces appels nécessite de tester chaque résultat. Rust vous permet de gérer les erreurs de deux manières :

  1. En utilisant la macro try! ;
  2. En utilisant l'opérateur ? (qui est l'équivalent de try! mais intégré directement au langage).

La macro try! vient envelopper la fonction (ou la macro) cible comme ceci :

 
Sélectionnez
// On 'test' write! Pour voir si une erreur survient. S'il y a une erreur, 
// elle sera renvoyée. Sinon, l'exécution continue. 
try!(write!(f, "{}", value));

L'opérateur ?, bien qu'équivalent à la macro try!, vient se positionner devant l'appel de la fonction (ou macro).

 
Sélectionnez
write!(f, "{}", value)?;

Avec l'opérateur ?, l'implémentation du trait fmt::Display pour un Vec est simple et lisible.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
use std::fmt; // On importe le module `fmt`.


// On déclare une structure nommée 'List' qui contient un 'Vec'.
struct List(Vec<i32>);

impl fmt::Display for List {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // On extrait le premier champ de la structure. 
        // Nous créons une référence de 'vec'.
        let vec = &self.0;

        write!(f, "[")?;

        // On parcourt 'vec' en stockant chacun de ses éléments et 
        // le nombre d'itérations.
        for (count, v) in vec.iter().enumerate() {
            // Pour tout élément, excepté le premier, on ajoute une virgule.
            // On utilise l'opérateur ?, ou la macro try!, pour renvoyer les erreurs.
            if count != 0 { write!(f, ", ")?; }
            write!(f, "{}", v)?;
        }

        // On ferme le crochet ouvert précédemment et on renvoie une instance 
        // de la structure `fmt::Result`.
        write!(f, "]")
    }
}

fn main() {
    let v = List(vec![1, 2, 3]);
    println!("{}", v);
}

Activité

Essayez de modifier le programme pour que l'index de chaque élément du vector soit également affiché durant l'exécution. Le résultat devrait ressembler à ceci :

 
Sélectionnez
[0: 1, 1: 2, 2: 3]

Voir aussi

La boucle for, le pattern ref, Result, les structures, try!, vec!.

1-2-3. Formatage

Nous avons vu que le formatage désiré était spécifié par des « chaînes de formatage » :

  • format!("{}", foo) -> "3735928559" ;
  • format!("0x{:X}", foo) -> "0xDEADBEEF" ;
  • format!("0o{:o}", foo) -> "0o33653337357".

La même variable (foo) peut être formatée de différentes manières suivant le type d'argument utilisé dans le marqueur (e.g. X, o, rien).

Cette fonctionnalité est implémentée à l'aide de traits, et il y en a un pour chaque type d'argument. Le plus commun est, bien entendu, Display. Il est chargé de gérer les cas où le type d'argument n'est pas spécifié (i.e. {}).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
use std::fmt::{self, Formatter, Display};

struct City {
    name: &'static str,
    // Latitude
    lat: f32,
    // Longitude
    lon: f32,
}

impl Display for City {
    // `f` est un tampon, cette méthode écrit la chaîne de caractères
    // formattée à l'intérieur de ce dernier.
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let lat_c = if self.lat >= 0.0 { 'N' } else { 'S' };
        let lon_c = if self.lon >= 0.0 { 'E' } else { 'W' };

        // `write!` est équivalente à `format!`, à l'exception qu'elle écrira
        // la chaîne de caractères formatée dans un tampon (le premier argument).
        write!(f, "{}: {:.3}°{} {:.3}°{}",
               self.name, self.lat.abs(), lat_c, self.lon.abs(), lon_c)
    }
}

#[derive(Debug)]
struct Color {
    red: u8,
    green: u8,
    blue: u8,
}

fn main() {
    for city in [
        City { name: "Dublin", lat: 53.347778, lon: -6.259722 },
        City { name: "Oslo", lat: 59.95, lon: 10.75 },
        City { name: "Vancouver", lat: 49.25, lon: -123.1 },
    ].iter() {
        println!("{}", *city);
    }
    for color in [
        Color { red: 128, green: 255, blue: 90 },
        Color { red: 0, green: 3, blue: 254 },
        Color { red: 0, green: 0, blue: 0 },
    ].iter() {
        // Utilisez le marqueur `{}` une fois que vous aurez implémenté
        // le trait fmt::Display.
        println!("{:?}", *color)
    }
}

N'hésitez pas à consulter la liste complète des traits dédiés au formatage ainsi que leurs types d'argument dans la documentation du module std::fmt.

Activité

Implémentez le trait fmt::Display pour la structure Color dans l'exemple ci-dessus de manière à obtenir un résultat identique à celui-ci :

 
Sélectionnez
RGB (128, 255, 90) 0x80FF5A
RGB (0, 3, 254) 0x0003FE
RGB (0, 0, 0) 0x000000

Indices :

Voir aussi

std::fmt


précédentsommairesuivant

Licence Creative Commons
Le contenu de cet article est rédigé par Rust Core Team et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.