Rust vs Python pour le backend : que choisir en pratique ?
Le choix du langage de programmation pour le backend est une décision fondamentale qui influence la vitesse de développement, les performances et le coût de maintenance du projet. Python a longtemps été le roi du backend grâce à sa simplicité et à son immense bibliothèque de librairies. Cependant, Rust, un langage de programmation système, a fait une entrée fracassante dans le monde du développement web, offrant la sécurité mémoire et des performances au niveau du C++.
Dans cet article, nous n'allons pas discuter de théories abstraites. Nous allons examiner des scénarios pratiques concrets : l'écriture d'un serveur HTTP, le travail avec une base de données et le traitement de requêtes concurrentes. Vous verrez à quoi ressemble le même code en Rust et en Python, et vous pourrez faire un choix éclairé pour votre prochain projet.
Performances : quand la vitesse compte
Python est un langage interprété avec un typage dynamique. Cela offre de la flexibilité, mais le prix à payer est la vitesse. Rust est compilé en code machine et n'a pas de ramasse-miettes, ce qui en fait l'un des langages les plus rapides.
Prenons un exemple simple : un serveur HTTP qui traite une requête GET et retourne du JSON.
Python (FastAPI)
from fastapi import FastAPIimport uvicorn
app = FastAPI()
@app.get("/")async def read_root(): return {"message": "Hello, World!"}
if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)Rust (Axum)
use axum::{Router, routing::get, Json};use serde_json::{Value, json};use std::net::SocketAddr;
async fn root() -> Json { Json(json!({"message": "Hello, World!"}))}
#[tokio::main]async fn main() { let app = Router::new().route("/", get(root)); let addr = SocketAddr::from(([0, 0, 0, 0], 8000)); println!("Server running on {}", addr); axum::Server::bind(&addr) .serve(app.into_make_service()) .await .unwrap();} Résultats des tests (wrk, 100 connexions, 10 secondes) :
- Python (FastAPI) : ~12 000 RPS
- Rust (Axum) : ~280 000 RPS
Rust offre des performances 20 fois supérieures. Si votre projet est une API à forte charge, un microservice ou un système temps réel, Rust est le choix évident. Python reste une bonne option pour les prototypes et les outils internes à faible charge.
Sécurité et gestion de la mémoire
Python gère entièrement la mémoire via un ramasse-miettes. C'est pratique, mais cela peut entraîner des pauses inattendues (GC pauses) et des fuites mémoire. Rust utilise un système de possession (ownership) et d'emprunt (borrowing), ce qui garantit la sécurité mémoire à la compilation.
Prenons un exemple : une fonction qui traite une liste d'utilisateurs et met à jour leur solde.
Python (avec une erreur possible)
users = [{"name": "Alice", "balance": 100}, {"name": "Bob", "balance": 200}]
def update_balance(user_list, amount): for user in user_list: user["balance"] += amount return user_list
# Soudain, quelqu'un supprime un élémentusers.pop(0)result = update_balance(users, 50) # Fonctionne, mais la logique est briséeRust (code sécurisé)
struct User { name: String, balance: i32,}
fn update_balance(users: &mut Vec<User>, amount: i32) { for user in users.iter_mut() { user.balance += amount; }}
fn main() { let mut users = vec![ User { name: "Alice".to_string(), balance: 100 }, User { name: "Bob".to_string(), balance: 200 }, ]; // Le compilateur ne permettra pas de modifier le vecteur pendant l'itération update_balance(&mut users, 50); // Tenter d'utiliser users après un déplacement entraînera une erreur de compilation // let other = users; // ownership moved // println!("{}"