Apprendre Rust à travers dix projets, des outils CLI aux services d’inférence
Une échelle graduée de dix projets Rust, allant d’une interface en ligne de commande pour scanner des fichiers à un clone de Redis, ainsi qu’un service d’interprétation et d’inférence, avec ce que chacun d’eux enseigne sur la mémoire, la concurrence et les pannes.
Sources: 10 Rust Projects That Will Actually Make You Better at Programming in 2026
De nombreux développeurs qui découvrent Rust passent des semaines à étudier la syntaxe et les tutoriels, puis stagnent dès qu’ils tentent de créer quelque chose de concret. Connaître les mots-clés ne revient pas à comprendre pourquoi le vérificateur d’emprunt rejette votre conception, comment les tâches asynchrones partagent des états, ou ce qui se passe lorsque un socket se ferme au milieu d’une requête. La façon fiable de combler cette lacune consiste à créer des projets qui rendent ces questions inévitables. Ce guide présente dix tels projets classés par ordre croissant de difficulté, explique ce que chacun vous oblige à apprendre, et ajoute les habitudes (itération, testage, benchmarking et utilisation disciplinée de l’IA) qui transforment un projet terminé en véritable compétence.
Pourquoi les projets valent mieux que la mémorisation de la syntaxe
Rust est utilisé dans les logiciels système, l’infrastructure cloud, les outils de développement, les produits de sécurité, les plateformes blockchain ainsi que dans certaines composantes de l’infrastructure d’intelligence artificielle. Cette diversité en fait une langue attrayante à apprendre, mais elle rend également facile une approche superficielle. Vous pouvez mémoriser chacun de ces termes clés :
let
mut
fn
struct
enum
trait
impl
et pourtant vous bloquer lorsque un programme réel a besoin d’un état mutable partagé entre plusieurs threads. Les projets d’apprentissage les plus utiles ne sont pas nécessairement de grande envergure. Ce sont ceux qui vous aident à comprendre pourquoi le langage a été conçu de cette manière, car ils vous placent dans des situations où une approche naïve ne compile pas ou échoue face à des données réelles.
1. Un outil en ligne de commande qui résout un problème réel
Commencez par quelque chose de simple et d’utile dans la pratique. Parmi les bons choix, on trouve un analyseur de journaux, un outil de recherche de fichiers, un afficheur JSON, un processeur CSV, un générateur de mots de passe, un scanner de dossiers ou un convertisseur Markdown.
Un scanner de journaux, par exemple, pourrait être utilisé de la manière suivante :
$ logscan app.log --level error
et afficher un résumé suivi des lignes correspondantes (la première ligne de résultat est affichée ici avec le résumé) :
Found 42 errors12:31:04 Database connection failed
12:31:09 Request timeout
12:31:15 Authentication service unavailable
Même un outil de cette taille fait appel à un nombre surprenant d’aspects du langage :
- l’analyse des arguments en ligne de commande
- la lecture de fichiers et la gestion des erreurs d’entrée/sortie
- le travail avec
Stringet&str - la modélisation des données à l’aide de structures et d’enum
- la propagation des erreurs via
Resultet? - l’organisation du code en modules
- la rédaction de tests unitaires
Il est tout aussi important qu’il soit suffisamment petit pour être achevé. Envoyer un produit complet est une compétence à part entière, et un outil petit mais terminé enseigne bien plus qu’un projet ambitieux abandonné.
2. Un serveur HTTP minimal sans framework
Client
↓
TCP connection
↓
HTTP parser
↓
Router
↓
Handler
↓
Response
Au cours de ce processus, vous découvrirez TCP et les sockets, la structure des messages HTTP, le parsing des requêtes, la création de réponses, la gestion des buffers, la responsabilité des données circulant entre les différentes étapes, ainsi que la gestion de plusieurs connexions en même temps. Le serveur n’a pas besoin d’être prêt pour la production ; l’objectif est de comprendre le fonctionnement. Une fois que vous aurez fait cela manuellement, des frameworks tels qu’Axum ou Actix Web ne sembleront plus magiques, car vous saurez exactement quelles tâches ils prennent en charge à votre place.
3. Une API REST soutenue par une base de données réelle
Ensuite, approchons-nous d’un backend réel. Une API de gestion de tâches constitue un bon sujet, avec les routes de ressources habituelles :
POST /tasks
GET /tasks
GET /tasks/:id
PUT /tasks/:id
DELETE /tasks/:id
Ensuite, ajoutez PostgreSQL, l’authentification, la validation, des réponses d’erreur structurées, le journalisation, la gestion de la configuration et des tests d’intégration. C’est là que les caractéristiques propres à chaque langage commencent à s’assembler pour former une architecture, et c’est aussi là que se posent les questions les plus importantes : quel niveau doit gérer une logique donnée, que doit faire l’API lorsque la base de données est indisponible, quel statut et quel corps le client doit recevoir, comment les erreurs doivent être transmises du niveau de stockage à la réponse HTTP, et en quoi la configuration doit différer selon les environnements. Ces questions de conception sont bien plus importantes que n’importe quelle règle syntaxique isolée.
4. Un robot de recherche web concurrentiel
Maintenant, augmentons le niveau de difficulté. Un crawler qui récupère de nombreuses pages en même temps s’étend à partir d’un point de départ comme ceci :
┌── URL 1
│
Start ───────┼── URL 2
│
├── URL 3
│
└── URL 4
Imposez un plafond explicite au parallélisme :
Maximum concurrent requests: 10
Cette seule contrainte vous oblige à comprendre la concurrence plutôt que de simplement créer des tâches en espérant le meilleur. Vous travaillerez avec des fonctions et des tâches asynchrones, des canaux, un état partagé ainsi que la synchronisation nécessaire, des délais d’expiration, des tentatives de réessai et une limitation de vitesse. Vous rencontrerez également des problèmes qui n’apparaissent jamais dans les programmes séquentiels, tels que la visite de la même URL deux fois depuis deux tâches ou un hôte lent qui ralentit les autres. C’est précisément en rencontrant ces problèmes que le projet devient intéressant à développer.
5. Un stockage clé-valeur
Une petite base de données est l’un des projets d’apprentissage les plus gratifiants. Commencez avec trois commandes :
SET name Rothy
GET name
DELETE name
Puis développez-le en y intégrant la persistance sur disque, l’indexation, la sérialisation, l’expiration des clés, les transactions ainsi qu’un accès concurrent sécurisé. Vous ne cherchez pas à rivaliser avec Redis ; vous essayez plutôt de comprendre le fonctionnement des moteurs de stockage. Ce projet met en pratique les structures de données, les opérations d’entrée/sortie sur fichiers, la disposition en mémoire, les formats de sérialisation, la concurrence, les garanties de durabilité et les performances. C’est également là que le modèle de propriété de Rust commence à sembler utile plutôt qu’obstruant, car vous gérez désormais un état à long terme auquel plusieurs parties du programme souhaitent accéder.
6. Un serveur Redis en miniature
Si le stockage clé-valeur vous intéresse, transformez-le en un petit serveur réseau qui utilise un protocole similaire à Redis :
SET user:1 Rothy
GET user:1
DEL user:1
et prenez en charge l’expiration basée sur le temps :
EXPIRE user:1 60
Au fil du temps, vous pouvez ajouter des connexions TCP depuis plusieurs clients, un analyseur de commandes, une gestion concurrente des requêtes, un stockage en mémoire, une persistance, des mécanismes d’expiration ainsi que des tests de performance. Le projet combine les réseaux, les structures de données, la concurrence et la conception de systèmes, et vous pousse à réfléchir aux compromis à faire. Le serveur doit-il s’optimiser pour la latence, le débit, l’empreinte mémoire ou la durabilité ? Il n’existe rarement une seule réponse correcte, et apprendre à choisir consciemment, ainsi qu’à justifier ce choix, constitue l’une des leçons les plus transférables de cette liste.
7. Une file d’attente de tâches avec des travailleurs
Une file d’attente de tâches est une excellente pratique pour les systèmes backend. Le flux de base est le suivant :
Producer
↓
Job Queue
↓
Workers
↓
Results
avec des tâches représentant des unités de travail telles que :
send_email
generate_report
process_image
run_test
Commencez par des travailleurs qui récupèrent répétitivement la tâche suivante et la exécutent. Ajoutez ensuite plusieurs travailleurs, des tentatives de réexécution, un suivi de l’état des tâches, des délais d’attente, un traitement des échecs et une fermeture propre, jusqu’à ce que la conception ressemble à ceci :
Producer
↓
┌───────────────┐
│ Job Queue │
└───────────────┘
↓ ↓ ↓
Worker Worker Worker
La leçon fondamentale est que le logiciel ne concerne pas seulement le parcours sans problème. Une file d’attente de tâches vous oblige à déterminer ce qui se passe lorsque une tâche échoue en cours d’exécution, lorsqu’un travailleur plante ou lorsque le processus doit s’arrêter alors qu’il y a encore des tâches en cours. Concevoir pour ces scénarios est ce qui distingue une démonstration d’un système réel.
8. Un agrégateur de journaux
Pour quelque chose qui ressemble davantage à l’ingénierie en production, créez un petit service de collecte de journaux. Plusieurs applications envoient leurs journaux vers un collecteur, qui les stocke :
Application A ──┐
Application B ──┼──> Log Collector ──> Storage
Application C ──┘
Le collecteur peut accepter des journaux via HTTP ou TCP brut et exposer une API de consultation :
GET /logs?level=error
GET /logs?service=payment
GET /logs?from=...
Ce travail couvre les réseaux, la sérialisation, l’ingestion concurrentielle, le filtrage, la gestion des timestamps, la structure de stockage, les performances et l’observabilité. C’est également un endroit idéal pour comprendre le comportement d’un service sous charge : ce qui se passe lorsque l’ingestion dépasse les capacités de stockage, quel est le coût en mémoire pour le tamponnage, et comment la vitesse des requêtes évolue à mesure que les données augmentent.
9. Une petite shell Unix
Ce projet est déroutant, et c’est justement ce qui le rend utile. Commencez par exécuter des commandes simples :
$ echo hello
hello
et gérer les commandes intégrées ainsi que les programmes externes (l’invite de la deuxième commande a été fusionnée ici avec le résultat de la première) :
$ pwd
/home/user$ ls
file1.txt
file2.txt
Ensuite, ajoutez l’exécution de commandes, les variables d’environnement, les pipes, la redirection, les processus en arrière-plan et la gestion des signaux, jusqu’à obtenir des pipelines tels que :
cat access.log | grep ERROR | sort
À ce stade, vous programmez en étroite relation avec le système d’exploitation. Vous apprenez comment les processus sont créés et gérés, comment stdin, stdout et stderr sont connectés, comment les variables d’environnement sont héritées, comment les pipes relient les processus, ainsi quels API du système d’exploitation se trouvent en dessous. Peu de projets permettent de mieux comprendre ce qui se passe réellement lorsque vous appuyez sur Entrée dans une terminal.
10. Un petit service d’inférence IA
Le projet final reflète l’évolution de la nouvelle infrastructure en cours de développement. L’objectif n’est pas d’entraîner un modèle, mais de construire un service autour d’un tel modèle :
Client
↓
Rust API
↓
Request Queue
↓
Inference Worker
↓
Model
↓
Response
Un service utile accepte les demandes d’inférence, valide les entrées, met les tâches en file d’attente, traite les demandes de manière concurrente, renvoie des résultats, enregistre la latence, applique des délais d’attente et expose des métriques. L’aspect technique intéressant réside dans l’infrastructure, et non dans le modèle. Vous pouvez expérimenter avec des limites de concurrence, le regroupement des demandes, le cache, le contrôle d’accès, l’utilisation de la mémoire, les budgets de latence, les tentatives répétées et l’observabilité. C’est là que le contrôle exercé par Rust sur la mémoire et la concurrence devient directement pertinent pour les systèmes sous-jacents aux applications d’IA.
Approfondissez un projet plutôt que de couvrir tous les dix
Vous n’avez pas besoin de développer chaque projet ici, et essayer de le faire est généralement une erreur. Choisissez-en un, terminez-le, puis continuez à l’améliorer. Un premier jalon pourrait être :
Version 1
Basic functionality
suivi de versions successives qui ajoutent chacune une nouvelle fonctionnalité (les flèches et les étiquettes de version apparaissent ensemble dans cette liste) :
↓Version 2
Better error handling ↓Version 3
Tests ↓Version 4
Concurrency ↓Version 5
Persistence ↓Version 6
Benchmarking ↓Version 7
Production-style monitoring
La majeure partie de l’apprentissage a lieu au sein de ce cycle d’amélioration. Chaque version vous oblige à reconsidérer des décisions antérieures, et le refactoring de votre propre code selon de nouvelles exigences enseigne bien plus que de commencer un autre projet à zéro.
Les questions auxquelles ces projets vous apprennent à répondre
Apprendre Rust n’est pas vraiment l’objectif ; apprendre à réfléchir au logiciel l’est. Les projets mentionnés ci-dessus vous confrontent constamment aux mêmes types de questions :
- Mémoire : qui possède ces données, et pendant combien de temps existent-elles ?
- Concurrence : que se passe-t-il si deux tâches y accèdent en même temps ?
- Erreurs : que doit se passer lorsque cette opération échoue ?
- Réseaux : que se passe-t-il si le client se déconnecte en cours de route ?
Ces questions s’appliquent à n’importe quel langage. Rust vous oblige simplement à les aborder beaucoup plus tôt, souvent au moment de la compilation, plutôt que en production.
Une progression suggérée
Si vous débutez, montez les échelons progressivement plutôt que de sauter directement au niveau le plus difficile :
- Débutant : l’outil CLI, pour les bases du langage et la gestion des erreurs.
- Débutant à intermédiaire : le serveur HTTP, pour les réseaux et les concepts de bas niveau.
- Intermédiaire : l’API REST, pour une véritable architecture backend.
- Intermédiaire : le crawler concurrent, pour le code asynchrone et la concurrence.
- Intermédiaire à avancé : le stockage clé-valeur, pour le stockage et l’état.
Tests dès le premier jour
N’attendez pas la fin pour effectuer des tests. Pour chaque projet, couvrez le comportement normal, les entrées invalides, les entrées vides, les conditions limites, les opérations concurrentes, les scénarios d’échec, les délais d’attente et les mécanismes de récupération. Si une extrémité requiert un ID d’utilisateur, un seul test de parcours réussi comme celui-ci n’est pas suffisant :
GET /users/123
Testez également les nombres zéro, négatifs, non numériques, les IDs très grands ainsi que le cas où un segment manque :
GET /users/0
GET /users/-1
GET /users/abc
GET /users/999999999
GET /users/
C’est ici que la programmation et les tests se fondent en une seule discipline. Un programme qui ne fonctionne que lorsque tout se passe bien n’est pas encore terminé. Le cargo test intégré à Rust permet de gérer facilement ce type de cas en même temps que le code.
Benchmark plutôt que suppositions
Rust jouit d’une réputation de vitesse, il est donc préférable de la mesurer plutôt que de l’affirmer. Un test de charge sur votre serveur peut produire un rapport similaire au suivant ; les chiffres sont indicatifs, et les vôtres dépendront entièrement du matériel et de la charge de travail :
Requests: 100,000
Average: 4.2 ms
P95: 7.1 ms
P99: 11.4 ms
Throughput: 23,800 req/s
Enregistrez une valeur de référence, modifiez un élément, puis mesurez à nouveau. Prêtez attention aux latences extrêmes telles que P95 et P99, et non seulement à la moyenne, car c’est là que les problèmes de concurrence et d’allocation apparaissent généralement. Mesurer avant et après est l’habitude qui permet d’ancrer les optimisations dans des preuves plutôt que dans des suppositions.
Utilisez des assistants IA sans négliger l’apprentissage
Les outils de codage basés sur l’IA peuvent désormais générer pour vous une grande quantité de code en Rust : des structures, des gestionnaires d’API, des tests, des requêtes de base de données, des types d’erreurs et de la documentation. Cela est vraiment utile, mais si l’objectif du projet est d’améliorer ses compétences de programmeur, confier toute la réflexion à l’IA va à l’encontre de cet objectif. Une meilleure méthode consiste à concevoir soi-même la solution, à l’implémenter, puis seulement à demander à un assistant de la vérifier, par exemple en lui demandant d’inspecter les problèmes liés à la propriété, les erreurs de concurrence, les lacunes dans le traitement des erreurs et les allocations inutiles. Ce type de vérification vous apprend quelque chose ; demander une analyse complète de l’application, en revanche, non. Il ne s’agit pas d’éviter l’IA, mais de l’utiliser sans renoncer à apprendre.
Quel est le champ d’application de l’ensemble
Même en terminant correctement trois ou quatre de ces projets, vous serez exposé à une grande variété des aspects de l’ingénierie logicielle :
Rust
│
├── Ownership
├── Borrowing
├── Error handling
├── Traits
├── Generics
│
├── Networking
├── Concurrency
├── Async programming
│
├── Databases
├── APIs
├── Operating systems
│
├── Testing
├── Benchmarking
├── Observability
│
└── System design
Cette portée vaut bien plus qu’un autre tutoriel terminé.
Points clés
- Choisissez des projets qui vous obligent à faire face à la responsabilité, à la concurrence et aux pannes, et non seulement à la syntaxe.
- Commencez par quelque chose de petit que vous pouvez terminer, puis progressez vers les réseaux, le stockage et les tâches au niveau du système d’exploitation.
- Itérez sur un seul projet à travers plusieurs versions plutôt que de démarrer beaucoup d’ projets.
- Testez les cas limites et les scénarios de panne dès le début, et évaluez chaque amélioration prétendue.
- Laissez l’IA examiner votre conception et votre code, mais réalisez vous-même la conception.
- Le meilleur projet suivant est celui qui vous fait comprendre quelque chose que vous ne comprenez pas actuellement : commencez petit, brisez-le, testez-le, analysez-le, corrigez-le, puis rendez-le plus difficile.