Aller au contenu

Frameworks et écosystème Rust

L'écosystème Rust pour le développement web et système s'est consolide autour de quelques crates incontournables. Contrairement a d'autres langages, Rust n'a pas de framework "officiel" : la communauté a converge vers des solutions complementaires qui composent bien entre elles, notamment grâce au runtime async Tokio et au système de traits de la stdlib.


Comparatif des frameworks web

Framework Approche Async runtime Points forts Cas d'usage typique
Actix Web Actor model Tokio Très haute performance, mature, vaste API APIs haute fréquence, microservices
Axum Tower/Hyper Tokio Ergonomique, extracteurs puissants, idiomatique APIs REST modernes, intégration Tokio
Rocket Macros Tokio API intuitive, guards, formes Prototypage rapide, petits services
Warp Filtres composables Tokio Très modulaire, testable unitairement APIs avec logique de routage complexe

Axum — framework web idiomatique

Axum est développé par l'équipe Tokio et est considéré comme le framework le plus idiomatique aujourd'hui. Il s'appuie directement sur Tower pour le middleware et Hyper pour le transport HTTP.

// Cargo.toml
// [dependencies]
// axum = "0.7"
// tokio = { version = "1", features = ["full"] }
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"

use axum::{
    extract::{Path, State},
    http::StatusCode,
    response::Json,
    routing::{get, post},
    Router,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;

#[derive(Clone, Serialize, Deserialize)]
struct Item {
    id: u32,
    nom: String,
}

// Etat partage entre les handlers via Arc<RwLock<_>>
type SharedState = Arc<RwLock<Vec<Item>>>;

// Handler GET /items — retourne la liste
async fn lister_items(State(state): State<SharedState>) -> Json<Vec<Item>> {
    let items = state.read().await;
    Json(items.clone())
}

// Handler GET /items/:id — retourne un item ou 404
async fn get_item(
    Path(id): Path<u32>,
    State(state): State<SharedState>,
) -> Result<Json<Item>, StatusCode> {
    let items = state.read().await;
    items
        .iter()
        .find(|i| i.id == id)
        .cloned()
        .map(Json)
        .ok_or(StatusCode::NOT_FOUND)
}

#[tokio::main]
async fn main() {
    let state: SharedState = Arc::new(RwLock::new(vec![
        Item { id: 1, nom: "Premier item".into() },
    ]));

    let app = Router::new()
        .route("/items", get(lister_items))
        .route("/items/:id", get(get_item))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Actix Web — performance maximale

Actix Web est l'un des frameworks web les plus rapides tous langages confondus selon les benchmarks TechEmpower. Son modèle est base sur des acteurs Actix, mais son API publique ressemble a celle d'Axum.

// Cargo.toml
// [dependencies]
// actix-web = "4"
// serde = { version = "1", features = ["derive"] }

use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct Payload {
    message: String,
}

// Macro #[get] — associe la fonction a la route GET /ping
#[get("/ping")]
async fn ping() -> impl Responder {
    HttpResponse::Ok().json(Payload {
        message: "pong".into(),
    })
}

// Handler avec extraction du corps JSON
#[post("/echo")]
async fn echo(body: web::Json<Payload>) -> impl Responder {
    HttpResponse::Ok().json(body.into_inner())
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(ping)
            .service(echo)
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}

Tokio — runtime async de référence

Tokio est le runtime asynchrone le plus utilisé dans l'écosystème Rust. Il fournit un scheduler multi-thread, des I/O non bloquantes, des timers et des primitives de synchronisation.

// Cargo.toml
// [dependencies]
// tokio = { version = "1", features = ["full"] }

use tokio::{
    fs::File,
    io::{AsyncReadExt, AsyncWriteExt},
    time::{sleep, Duration},
};

// async fn — fonction asynchrone Tokio
// Les points d'attente (.await) cedent le controle au scheduler
async fn lire_fichier(chemin: &str) -> Result<String, std::io::Error> {
    let mut fichier = File::open(chemin).await?;   // I/O non bloquante
    let mut contenu = String::new();
    fichier.read_to_string(&mut contenu).await?;
    Ok(contenu)
}

async fn tache_periodique() {
    loop {
        println!("Tick");
        sleep(Duration::from_secs(1)).await;  // Attend sans bloquer le thread
    }
}

#[tokio::main]
async fn main() {
    // Lancer plusieurs taches concurrentes avec tokio::spawn
    let handle = tokio::spawn(tache_periodique());

    // tokio::join! execute des futures en parallele
    let (r1, r2) = tokio::join!(
        lire_fichier("/etc/hostname"),
        lire_fichier("/etc/os-release"),
    );

    println!("hostname : {:?}", r1);
    handle.abort(); // Arret de la tache periodique
}

Tokio vs async-std

Tokio est le choix standard pour les projets en production. async-std propose une API plus proche de la stdlib mais est moins activement maintenu. La majorité des crates de l'écosystème (Axum, sqlx, reqwest) ciblent Tokio en priorité.


Rocket — ergonomie et guards

Rocket est apprecie pour la clarte de son API. Ses "request guards" permettent d'injecter des valeurs validees directement dans les parametres des handlers.

// Cargo.toml
// [dependencies]
// rocket = { version = "0.5", features = ["json"] }
// serde = { version = "1", features = ["derive"] }

#[macro_use]
extern crate rocket;

use rocket::serde::{json::Json, Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
struct Reponse {
    statut: String,
    valeur: u32,
}

// #[get] et les parametres de route sont associes par nom
#[get("/calcul/<a>/<b>")]
fn additionner(a: u32, b: u32) -> Json<Reponse> {
    Json(Reponse {
        statut: "ok".into(),
        valeur: a + b,
    })
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/api", routes![additionner])
}

CLI — Clap

Clap est la référence pour le parsing des arguments en ligne de commande. Son API dérivé permet de définir la CLI directement à partir d'une struct.

// Cargo.toml
// [dependencies]
// clap = { version = "4", features = ["derive"] }

use clap::{Parser, Subcommand};

/// Outil de gestion de fichiers — exemple Clap 4
#[derive(Parser)]
#[command(name = "monoutil", version, about)]
struct Cli {
    /// Niveau de verbosity (-v, -vv, -vvv)
    #[arg(short, long, action = clap::ArgAction::Count)]
    verbeux: u8,

    #[command(subcommand)]
    commande: Commande,
}

#[derive(Subcommand)]
enum Commande {
    /// Lister les fichiers dans un repertoire
    Lister {
        /// Chemin du repertoire (defaut : .)
        #[arg(default_value = ".")]
        chemin: String,
    },
    /// Compter les lignes d'un fichier
    Compter {
        /// Fichier a analyser
        fichier: String,
    },
}

fn main() {
    let cli = Cli::parse();

    match cli.commande {
        Commande::Lister { chemin } => {
            println!("Listage de : {}", chemin);
        }
        Commande::Compter { fichier } => {
            println!("Comptage dans : {}", fichier);
        }
    }
}

Écosystème complementaire

Crate Catégorie Rôle
serde Serialisation Framework de serialisation/deserialisation universel
serde_json Serialisation Support JSON pour serde
sqlx Base de données Requêtes SQL asynchrones avec vérification à la compilation
diesel Base de données ORM synchrone avec DSL de requêtes type-safe
reqwest HTTP client Client HTTP asynchrone base sur Tokio + Hyper
tower Middleware Abstraction Service/Layer pour les middlewares
tracing Observabilité Framework de traces et logs structures
anyhow Gestion d'erreurs Boite d'erreurs flexible pour les applications
thiserror Gestion d'erreurs Macros dérivé pour les types d'erreurs de bibliotheques
uuid Identifiants Génération et parsing d'UUIDs
chrono Date/heure Types date/heure avec fuseaux horaires
rayon Parallélisme Iterateurs parallèles data-parallelism
regex Expressions reg. Moteur de regex performant (NFA, pas de backtracking)
wasm-bindgen WebAssembly Bindings entre Rust/WASM et JavaScript

Serde et les dérivés

Serde utilise des macros procedurales (#[derive(Serialize, Deserialize)]) qui augmentent le temps de compilation. Dans les grands projets, il est recommande d'activer le cache incremental de Cargo et d'envisager serde avec default-features = false pour les cibles WASM ou embarquées.