Formation Rust

https://play.rust-lang.org/

Pourquoi faire du Rust ?

Pourquoi faire du Rust ?

  • Performance : pas de runtime/Garbage Collector. Pratique quand on veut de la vitesse ou pour des systèmes embarqués !
  • Fiabilité : le typage fort ainsi que son système d'ownership permet de garantir la memory safety
  • Interfaçable : possibilité de compiler vers une librairie type C ABI, d'utiliser de telles librairies ou encore de compiler vers du WebAssembly
  • Croissance : voté le langage le plus apprécié de StackOverflow en 2016, 2017 et 2018, il séduit de plus en plus d'entreprises !

Pourquoi faire du Rust ?

Un langage qui reste haut niveau:

// Sums all the positives values in `v`
fn sum_pos(v: &[i32]) -> i32 {
    let mut sum = 0;
    for i in v.iter().filter(|&i| *i > 0) {
        sum += i;
    }
    sum
}

On note la présence d'itérateurs et de closures

# Sums all the positives values in `v`
def sum_pos(v):
    sum = 0;
    for i in filter(lambda i: i > 0, v):
        sum += i
    return sum

Pourquoi faire du Rust ?

Un langage qui reste haut niveau:

// Sums all the positives values in `v`
fn sum_pos(v: &[i32]) -> i32 {
    v.iter()
     .filter(|&i| *i > 0)
     .map(|i| *i)
     .sum()
}

Pas bien plus compliqué que

# Sums all the positives values in `v`
def sum_pos(v):
    return sum([i in v if i > 0])

... Et peut-être plus lisible !

Pourquoi faire du Rust ?

Beaucoup de paradigmes de langages fonctionnels empruntés en Rust !

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

Performance

Performance

Toutes ces features n'empêchent pas Rust de rester un langage extrêmement rapide (blazingly fast) ! La plupart des benchmarks le place à égalité des applications C/C++.

Exemples : actix un microframework web, ripgrep un remplacement de GNU grep, ...

Aussi par là -> The Benchmark Game

Performance

De nombreuses entreprises utilisent Rust pour gagner en efficacité:

  • Mercurial réécrit de nombreuses parties de hg
  • Dropbox a créé son système de fichiers custom
  • Deliveroo, pour choisir vite à qui donner une livraison
  • npm pour accélérer son registre
  • OVH pour le management de log

Memory Safety

Memory Safety

Mais qu'est-ce que c'est ?

Un pointeur (addresse mémoire) lors de son utilisation doit pointer vers un endroit valide : la mémoire est allouée et de la bonne taille.

Le C/C++ permet un contrôle fin de la mémoire : malloc/free, new/delete. Au codeur de vérifier que tout va bien !

Le Python gère la mémoire à votre place avec un Garbage Collector. Il détecte pendant l'exécution quelles variables garder ou libérer.

Memory Safety

Comment peut-on avoir un langage qui donne le contrôle tout en restant safe et en ne perdant pas de performances ?

Pour cela, le compilateur Rust vérifie à la compilation que les pointeurs utilisés sont valides, même en cas de multithreading !

Cela nécessite donc de nouveaux concepts : l'ownership et le borrowing.

Et bien sûr, cela donne de nombreuses erreurs intimidantes du compilateur !

Memory Safety

a = [0,1,2,3,4]
for i in range(0,8):
    print(a[i])

IndexError: list index out of range

int a[5] = {0,1,2,3,4};
for (int i = 0; i < 8 ; i++) {
    printf("%d\n", a[i]);
}

Undefined Behaviour !

Avec -O0, on lit 3 cases mémoires qui ne sont pas à nous...

Avec -O3, la condition i < 8 est optimisée en true !

Memory Safety

Safe Rust: Panic! : index out of bounds

let a = [0,1,2,3,4];
for i in 0..8 {
    println!("{}", a[i]);
}

vs unsafe: tant que le code unsafe est correct et correctement appelé, le tout va bien !

let a = [0,1,2,3,4];
for i in 0..8 {
    // Take the address : safe
    let address = (&a[0] as *const i32);
    unsafe {
        // Unsafe to dereference a raw pointer
        println!("{}", *(address).offset(i) );
    }
}

Ownership

Ownership

Python

name = "Hail Ferris!"
z = name[0]
helper(name)
# I can still use name!
# I hope z is still valid (Python makes a copy though)

Rust

fn helper(name: String) {
    println!("{}", name);
}
let name = format!("Hail Ferris!");
helper(name);
helper(name); // I can no longer use name! Does not compile

Solutions : utiliser .clone() ou renvoyer name

Ownership

error[E0382]: use of moved value: `name`
 --> src/main.rs:6:12
  |
5 |     helper(name);
  |            ---- value moved here
6 |     helper(name);
  |            ^^^^ value used here after move
  |
  = note: move occurs because `name` has type `std::string::String`, which does not implement the `Copy` trait

Borrowing

Borrowing

2 types d'alias possibles:

  • mutable borrow : on peut lire et modifier la donnée, mais il ne peut y avoir aucun autre alias en même temps : &mut x
  • shared borrow : on peut lire la donnée, et ce avec autant d'alias que l'on veut &x

Borrowing

fn main() {
    let mut name = format!("Rust");
    update(&mut name);
    helper(&name);
    helper(&name);
}

fn helper(x: &String) {
    println!("{}", x);
}

fn update(x: &mut String) {
    x.push_str(" is great");
}

Ces vérification sont toujours appliquées au multithreading => plus de data races !