Rust : ce langage natif et puissant
Tags: rust
Published: 2021-01-26 09:26:29

<p>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é.</p>

 

<p>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 <em>frameworks</em> de la communauté web, le C++ souffre de quelques défauts qui deviennent récurrents, notamment :</p>

 

<ul>
    <li>Intégrer une bibliothèque peut être vraiment (<em>très</em>) fastidieux, même parfois jusqu'au point de préférer réimplémenter la fonctionnalité soi-même.</li>
    <li>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 <em>threads</em>...</li>
</ul>

 

<p>Rust, lui, ne souffre pas de ces défauts.</p>

 

<p style="text-align : center;"><img alt="Symbol Rust" src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Rust_programming_language_black_logo.svg/1200px-Rust_programming_language_black_logo.svg.png" width="150px" /></p>

 

<h2 id="la-philosophie-rust">La philosophie Rust</h2>

 

<p>Rust est un langage <strong>safe</strong> grâce à un système de <em>borrow checker</em> redoutable :</p>

 

<ul>
    <li>Les variables sont par défaut immutables.</li>
    <li>Chaque variable mutable ne peut être modifiée que dans un seul <em>scope</em> à la fois, c'est le concept d'<em>ownership</em>.</li>
    <li>La durée de vie est vérifiée pour chaque variable : le <em>lifetime</em>. 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.</li>
</ul>

 

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

 

<p>Rust a donc une syntaxe qui se veut <strong>explicite</strong> mais <strong>concise</strong>.</p>

 

<h2 id="avec-cargo-un-super-environnement-de-dev">Avec Cargo : un super environnement de dev</h2>

 

<h3 id="packages">Packages</h3>

 

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

 

<h3 id="tests">Tests</h3>

 

<p>Avec <code>cargo test</code> il est possible d'exécuter toutes les portions de code taggées&nbsp;<code>#[test]</code> pour les tests unitaires :</p>

 

```rust
#[test]
fn test_add() {
    assert_eq!(add(1, 2), 3);
}
```
 

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

 

<h3 id="compilation">Compilation</h3>

 

<p>Le code se compile avec <code>cargo build</code> ou directement <code>cargo run</code> pour les feignants ;)</p>

 

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

 

<p>Il est même possible de compiler en Web Assembly pour intégrer un traitement important côté client, comme expliqué dans ce <a href="https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm">tutoriel de Mozilla</a>.</p>

 

<h2 id="autres-concepts-cl-s-du-langage">Autres concepts clés du langage</h2>

 

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

 

<ul>
    <li>d'inférence de type : <code>let a = 1 + 2; // -&gt; i32</code></li>
    <li>de fonction lambda appelées <em>closures</em> :</li>
</ul>

```rust
let closure_annotated = |i: i32| -> i32 { i + 1 };
```

<ul>
    <li>de tuples : </li>
</ul>

```rust
let tuple = (1, "hello", 4.5, true);
```

<ul>
    <li>de structures :</li>
</ul>

```rust
let user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};
```

<p>Mais également d'objets bien spécifiques au langage :</p>

<ul>
    <li>les objets <code>Option</code> et <code>Result</code> qui servent à encapsuler de manière sûre le résultat d'une fonction</li>
    <li>les traits : on peut les comparer au <em>design pattern</em> "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 : <code>Debug</code>, <code>Format</code>, <code>Serialize</code>, <code>Clone</code>, <code>Copy</code>,...</li>
    <li><code>match</code> : les débutants croiront qu'il s'agit d'un simple <code>switch/case</code>. Erreur ! C'est bien plus que ça ! Un <code>match</code> ne se contente pas de tester des valeurs, mais des <strong>patterns</strong> !</li>
</ul>

 

```rust
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
}
```

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

 

<ul>
    <li>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 <em>patterns</em>, tel un parseur de code ! On les reconnaît facilement, car elles se terminent par un point d'exclamation, comme <code>println!(...)</code>. Voici par exemple la définition de la macro standard <code>vec![...]</code> :</li>
</ul>

 

```rust
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}
```

<p>Elle est très pratique pour instancier rapidement un conteneur <code>vec</code> directement avec des valeurs :</p>

```rust
let my_vec = vec![1, 2, 3, 10];
```

<p>... et bien d'autres !</p>

<h2 id="pour-aller-plus-loin">Pour aller plus loin</h2>

 

<p>La doc officielle pour apprendre le Rust : <a href="https://doc.rust-lang.org/rust-by-example/index.html">Rust by Example</a></p>

 

<p>Essayer du Rust en ligne, sans rien installer : <a href="https://play.rust-lang.org/">Rust Playground</a></p>