Acheter version papier

Rust

Fork me on GitHub

II. Spécificités de Rust

3. Les attributs

Il est possible d'ajouter des métadonnées sur des éléments dans un code. Ces informations peuvent servir à fournir des informations au compilateur ou bien à d'autres outils lisant ce code. Par-exemple, changer le nom de la crate :

Run#![crate_name = "nom_de_cagette"]

Les attributs ont 2 formes : externe et interne.

Dans l'exemple au dessus, c'est la forme interne qui est utilisée. Cela signifie que l'effet de l'attribut est appliqué sur l'élément dans lequel il se trouve. Ils s'écrivent sous la forme #![].

À l'inverse, la forme externe signifie que l'effet de l'attribut est appliqué sur l'élément qui suit. Ils s'écrivent sous la forme #[] (donc pas de !).

Exemples:

Run#![allow(non_camel_case_types)] // Appliqué sur le module courant.

#[allow(dead_code)] // Appliqué sur `module`.
mod module {
    #![allow(dead_code)] // Appliqué sur `module`.

    #[allow(unused_variables)] // Appliqué sur `fonction`.
    fn fonction() {}
}

Il y a 4 types d'attributs :

  • Les attributs intégrés au compilateur de Rust ("built-in")
  • Les attributs d'outils
  • Les macros attributs
  • Les attributs derive

Les attributs intégrés

Dans les 2 exemples ci-dessus, nous avons utilisé ces attributs. Comme il y en a beaucoup, nous n'allons n'en lister que quelques uns. La liste complète est ici.

allow, warn et deny

Quand vous compilez, Rust émet des messages d'avertissement ou d'erreur quand on compile. On les appelle des lints. Ces lints ont chacun un "niveau" que l'on peut changer grâce à ces attributs.

Par-exemple , unused_varibles est de niveau "warning" par défaut. Si on souhaite l'ignorer, on peut utiliser allow, par-contre si on veut qu'il arrête la compilation si jamais il est émis, on utilisera deny.

must_use

Cet attribut est très intéressant car quand il est utilisé sur un type, il rend l'utilisation de ce type "obligatoire" :

Run#[must_use]
struct Struct;

impl Struct {
    fn init() -> Struct {
        Struct
    }
}

fn main() {
    Struct::init();
}

Quand on compile ce code, le compilateur affiche :

warning: unused `Struct` that must be used
  --> src/main.rs:11:5
   |
11 |     Struct::init();
   |     ^^^^^^^^^^^^^^

Sur une fonction, il force l'utilisation de la valeur de retour :

Run#[must_use]
fn fonction() -> bool {
    true
}

fn main() {
    fonction();
}

Ce qui donne :

warning: unused return value of `fonction` that must be used
 --> src/main.rs:7:5
  |
7 |     fonction();
  |     ^^^^^^^^^^

Les attributs d'outils

Ces attributs ne sont pas fournis par le compilateur de Rust mais par des outils externes. Par-exemple rustfmt que l'on peut lancer avec la commande cargo fmt pour formatter notre code. Exemple :

Run#[rustfmt::skip]
fn fonction()
{
  let variable =     "12";
}

Avec #[rustfmt::skip], on dit à rustfmt de ne pas formatter l'élément qui suit (donc la fonction fonction).

Les macros attributs

Ces attributs sont uniquement des attributs externes. Ce sont, comme leur nom l'indique, des macros. Ce qui signifie qu'ils vont modifier l'élément sur lequel ils sont utilisés. Comme cela aborde des concepts plus avancés de Rust, ils sont abordés dans la dernière partie de ce livre, dans le chapitre "Les macros procédurales".

Les attributs derive

Ils sont aussi appelés les "derive macros" et sont uniquements des attributs externes. Nous les avons déjà évoqué dans le chapitre sur les traits donc rien de nouveau ici. Pour rappel, ils ressemblent à ça :

Run#[derive(Debug)]
pub struct Struct;

Ils permettent d'ajouter des implémentations sur des types. Dans l'exemple ci-dessus, on implémente le trait Debug sur notre structure Struct.

Tout comme les macros attributs, ce sont des macros. Ils sont aussi abordés dans la dernière partie de ce livre, dans le chapitre "Les macros procédurales".