Rust : ce langage natif et puissant

jnp95

Jean-Noël

Posted on January 26, 2021

Rust : ce langage natif et puissant

En 2020, quand on parle de langage "natif" ou bas niveau, étonnamment, on pense encore beaucoup au C et au C++. Bien que les dernières versions du C++ fassent évoluer la syntaxe, le fond même du langage n'a pas vraiment changé.

Et il faut bien avouer qu'en face de langages plus haut niveau comme le JavaScript ou le Python et l'évolution fulgurante de tous les frameworks de la communauté web, le C++ souffre de quelques défauts qui deviennent récurrents, notamment :

  • Intégrer une bibliothèque peut être vraiment (très) fastidieux, même parfois jusqu'au point de préférer réimplémenter la fonctionnalité soi-même.
  • Il y a souvent des problèmes d'accès mémoire : des pointeurs zombies, des erreurs de segmentation, des conflits d'accès à des ressources partagées entre plusieurs threads...

Rust, lui, ne souffre pas de ces défauts.

Symbol Rust

La philosophie Rust

Rust est un langage safe grâce à un système de borrow checker redoutable :

  • Les variables sont par défaut immutables.
  • Chaque variable mutable ne peut être modifiée que dans un seul scope à la fois, c'est le concept d'ownership.
  • La durée de vie est vérifiée pour chaque variable : le lifetime. Si le compilateur a un doute par exemple lorsqu'on compare 2 variables entre elles, il faudra spécifier de manière explicite la durée de vie de chaque variable.

Le compilateur rustc est strict. Vraiment strict. Dites vous bien que vous ne vous êtes jamais vraiment battu avec un compilateur tant que vous n'avez pas essayé rustc ! Mais la bonne nouvelle, c'est que ses messages d'erreurs sont explicites, et les suggestions aideront à résoudre les problèmes.

Rust a donc une syntaxe qui se veut explicite mais concise.

Avec Cargo : un super environnement de dev

Packages

Rust et son gestionnaire de paquets Cargo fonctionnent sous forme de modules hiérarchiques. Un programme Rust est donc structuré, et l'intégration d'une bibliothèque externe consiste simplement à ajouter nom_module=1.0.0 dans le Cargo.toml et à laisser la commande cargo install faire le reste ! (Hey les développeurs Node.js, ça vous rappelle quelque chose ?)

Tests

Avec cargo test il est possible d'exécuter toutes les portions de code taggées #[test] pour les tests unitaires :

#[test]
fn test_add() {
    assert_eq!(add(1, 2), 3);
}
Enter fullscreen mode Exit fullscreen mode

mais aussi tous les fichiers placés dans un répertoire test/ pour les tests d'intégration.

Compilation

Le code se compile avec cargo build ou directement cargo run pour les feignants ;)

Il est possible de faire de la cross-compilation en spécifiant la target (et en installant le nécessaire) : cargo build --target=arm-linux-androideabi

Il est même possible de compiler en Web Assembly pour intégrer un traitement important côté client, comme expliqué dans ce tutoriel de Mozilla.

Autres concepts clés du langage

Par ailleurs, le Rust est également doté (liste non exhaustive bien sûr):

  • d'inférence de type : let a = 1 + 2; // -> i32
  • de fonction lambda appelées closures :
let closure_annotated = |i: i32| -> i32 { i + 1 };
Enter fullscreen mode Exit fullscreen mode
  • de tuples :
let tuple = (1, "hello", 4.5, true);
Enter fullscreen mode Exit fullscreen mode
  • de structures :
let user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};
Enter fullscreen mode Exit fullscreen mode

Mais également d'objets bien spécifiques au langage :

  • les objets Option et Result qui servent à encapsuler de manière sûre le résultat d'une fonction
  • les traits : on peut les comparer au design pattern "Interface". Ils sont l'unique solution pour l'héritage de fonctions sur des structures de données. L'avantage en Rust, c'est que beaucoup de traits standards sont déjà implémentés, et il suffit alors de les spécifier sur une structure pour bénéficier de tous leurs bienfaits : Debug, Format, Serialize, Clone, Copy,...
  • match : les débutants croiront qu'il s'agit d'un simple switch/case. Erreur ! C'est bien plus que ça ! Un match ne se contente pas de tester des valeurs, mais des patterns !
let x = 'c';

match x {
    'a'..='i' => println!("early ASCII letter"), // with range pattern
    'j' => println("middle ASCII letter"),
    'k'..='z' => println!("late ASCII letter"),
    _ => println!("something else"), // all other values
}
Enter fullscreen mode Exit fullscreen mode

À noter aussi que le compilateur fera la tête tant que toutes les valeurs possibles de x ne seront pas gérées par le match ! Souvenez-vous, Rust est un langage safe !

  • macros (pour un public averti) : contrairement au C++, une macro n'est pas seulement du code qui se substituera à un symbole : c'est une instruction de pré-compilation qui va réagir à des patterns, tel un parseur de code ! On les reconnaît facilement, car elles se terminent par un point d'exclamation, comme println!(...). Voici par exemple la définition de la macro standard vec![...] :
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}
Enter fullscreen mode Exit fullscreen mode

Elle est très pratique pour instancier rapidement un conteneur vec directement avec des valeurs :

let my_vec = vec![1, 2, 3, 10];
Enter fullscreen mode Exit fullscreen mode

... et bien d'autres !

Pour aller plus loin

La doc officielle pour apprendre le Rust : Rust by Example

Essayer du Rust en ligne, sans rien installer : Rust Playground

💖 💪 🙅 🚩
jnp95
Jean-Noël

Posted on January 26, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related