Tokio, Axum et SQLx : interroger un développeur Rust back-end

Un développeur Rust back-end écrit des services web async : un runtime Tokio, un routeur Axum ou Actix Web, des requêtes SQLx vérifiées à la compilation. Le compilateur arrête une bonne partie des erreurs mémoire. Il laisse passer un calcul qui bloque le runtime ou un verrou tenu trop longtemps, et c'est sur ces points que l'expérience de production se voit.

Recevoir la fiche complète

Mis à jour le Par l'équipe Delectus5 min de lecture

La fiche Développeur Rust back-end : technologies, chaîne du poste, une question et la grille d'évaluation

Le poste consiste à construire des API HTTP ou gRPC et des workers qui consomment des files de messages. La pile la plus fréquente associe Tokio, Axum, SQLx sur PostgreSQL, Serde pour le JSON et tracing pour les logs. Actix Web reste courant sur des bases de code plus anciennes, Diesel quand l'équipe préfère un ORM typé. Rust sert souvent à un service précis où la latence ou la mémoire comptent, à côté de services écrits en Go, en Java ou en Python.

On trouve deux populations sur les CV Rust. Les profils système ou embarqué maîtrisent ownership et unsafe, mais connaissent peu le HTTP, les pools de connexions et les migrations de schéma. Les développeurs web venus de Go ou de TypeScript écrivent vite des handlers Axum et se perdent dans les lifetimes dès que le code se complique. Demandez ce qui arrive quand une tâche appelle du code bloquant, ou pourquoi un future n'est pas Send.

Le code qu'un candidat cite se date facilement. Axum 0.8 a changé la syntaxe des chemins (/{id} au lieu de /:id), async fn dans les traits est stable depuis Rust 1.75 et l'édition 2024 depuis Rust 1.85. Le PDF ajoute un cas de 20 minutes sur un service d'envoi de notifications.

Tokio, Axum, SQLx : la pile d'un service Rust

Tokio

Le runtime async le plus utilisé. Il fait tourner des milliers de tâches sur quelques threads, à condition qu'aucune ne bloque. Un calcul lourd ou une bibliothèque synchrone doit passer par spawn_blocking, sinon tout le service ralentit. C'est le premier sujet à vérifier.

Axum et Tower

Axum, maintenu par l'équipe de Tokio, est le framework HTTP le plus choisi pour un nouveau service. Les handlers reçoivent leurs paramètres par des extracteurs typés, et les middlewares viennent de Tower : timeout, limite de débit, compression, traces. La version 0.8 a changé la syntaxe des chemins, ce qui permet de dater le code qu'un candidat montre.

Actix Web

L'autre grand framework HTTP, rapide et mature, avec ses propres middlewares. On le trouve sur beaucoup de services écrits avant l'arrivée d'Axum. Il lance un runtime par thread de travail, ce qui change la façon de partager l'état entre les requêtes.

SQLx

Une bibliothèque d'accès SQL async, qui n'est pas un ORM. La macro query! vérifie chaque requête contre le schéma de la base au moment de la compilation, ce qui demande une base disponible ou un cache hors ligne en CI. Elle gère aussi le pool de connexions et les migrations, pour PostgreSQL, MySQL, MariaDB et SQLite.

Diesel

L'ORM typé historique de Rust, avec un générateur de requêtes contrôlé par le compilateur. Son API est synchrone, et l'usage en async passe par diesel-async ou par spawn_blocking. Demandez pourquoi l'équipe l'a préféré à SQLx, la réponse montre si le choix a été pensé.

Serde

La bibliothèque de sérialisation : JSON surtout, mais aussi TOML, YAML ou MessagePack. Ses attributs (rename_all, default, deny_unknown_fields, flatten) définissent le contrat de l'API. Une erreur à cet endroit casse les clients sans que le compilateur ne signale rien.

tracing et OpenTelemetry

tracing produit des logs structurés et des spans qui suivent une requête d'un bout à l'autre, y compris quand la tâche change de thread. Exportés en OpenTelemetry, ils donnent les traces distribuées. Sans eux, déboguer un service async en production revient à lire des lignes de logs mélangées.

thiserror et anyhow

thiserror sert à définir des types d'erreur précis dans les bibliothèques, anyhow à transporter des erreurs avec leur contexte dans le code applicatif. Côté HTTP, chaque erreur devient une réponse avec un statut et un message, sans fuite de détails internes.

Cargo et outillage

Cargo gère les dépendances, les workspaces et les features. clippy et rustfmt tournent en CI, cargo-deny ou cargo-audit surveillent les licences et les failles connues des crates. Le temps de compilation devient un vrai sujet sur un gros workspace.

Le contenu du poste dépend de la place de Rust dans le système. Quand Rust sert à un seul service exigeant, le développeur travaille entouré de services écrits dans d'autres langages : il expose une API stable, partage le même format de traces et suit les conventions de déploiement existantes. Quand toute la plateforme est en Rust, il choisit aussi les crates communes, l'organisation du workspace et les règles de revue. Les offres françaises restent peu nombreuses, et beaucoup associent Rust à C++, à Go ou à l'embarqué. Précisez dans l'annonce la part de back-end web et le framework utilisé, sinon vous recevrez surtout des profils système.

Les points à valider chez un développeur Rust back-end

  • Ownership, emprunts et lifetimes
  • Async et concurrence avec Tokio
  • API HTTP avec Axum ou Actix Web
  • Accès aux données avec SQLx ou Diesel
  • Gestion d'erreurs et contrats Serde
  • Observabilité, tests et performances

Questions d'entretien Rust sur l'async, les erreurs et les données

Chaque question vient avec ce qu'une bonne réponse contient et le signal qui doit vous alerter. Pas besoin d'être technique pour les poser : écoutez si le candidat raconte du vécu.

  1. Question 1 · confirmé

    Dans un handler Axum, un collègue verrouille un std::sync::Mutex puis appelle une API externe avec .await avant de relâcher le verrou. Qu'est-ce qui se passe ?

    Une bonne réponseLe garde du Mutex standard n'est pas Send. Le future du handler ne l'est donc plus, et Axum refuse de compiler la route. Si le code contourne le problème, le verrou reste pris pendant tout l'appel réseau et les autres requêtes attendent. La correction consiste à copier la donnée utile, à relâcher le verrou dans un bloc séparé, puis à faire l'appel. Le Mutex de Tokio ne sert que si le verrou doit vraiment traverser un await.

    Signal d'alerte« Je remplace par tokio::sync::Mutex partout » sans savoir pourquoi, ou aucune idée du lien entre verrou, await et Send.

  2. Question 2 · confirmé

    Un endpoint qui redimensionne des images fait chuter le temps de réponse de tout le service. Comment le diagnostiquez-vous et que changez-vous ?

    Une bonne réponseUn calcul long dans une tâche async occupe un thread du runtime, et les autres tâches planifiées sur ce thread attendent. On le voit avec tokio-console, ou dans des traces où des requêtes simples patientent sans raison. Le calcul part dans spawn_blocking ou dans un pool dédié comme rayon, avec une limite de concurrence (Semaphore) pour ne pas saturer la machine. Un senior propose aussi de confier ce travail à un worker séparé, alimenté par une file.

    Signal d'alerteAjouter des threads au runtime en espérant que cela suffise, ou ne faire aucune différence entre attendre une entrée-sortie et calculer.

  3. Question 3 · confirmé

    Comment transformez-vous les erreurs de votre code en réponses HTTP dans Axum ?

    Une bonne réponseUn type d'erreur applicatif, souvent un enum écrit avec thiserror, qui implémente IntoResponse. Chaque variante donne un statut (404, 409, 422, 500) et un corps JSON stable. L'opérateur ? propage les erreurs grâce à des implémentations de From. Les détails internes, message SQL ou contexte technique, partent dans les logs avec tracing et jamais dans la réponse. anyhow peut couvrir les 500, mais ne permet pas de choisir finement le statut.

    Signal d'alerteDes unwrap() dans les handlers, un panic qui coupe la connexion, ou le message d'erreur SQL renvoyé tel quel au client.

  4. Question 4 · confirmé

    Vous utilisez sqlx::query! partout. La CI échoue parce qu'elle n'a pas de base de données. Que faites-vous ?

    Une bonne réponseLe mode hors ligne : cargo sqlx prepare enregistre la description des requêtes dans un dossier .sqlx versionné avec le code, et la CI compile sans base. Ce cache se régénère à chaque changement de requête ou de schéma, et la CI vérifie qu'il est à jour avec cargo sqlx prepare --check. Autre solution, une base éphémère dans la CI qui applique les migrations avant la compilation.

    Signal d'alerteRevenir à des requêtes construites par concaténation de chaînes, ou abandonner les macros sans voir ce que l'on perd.

  5. Question 5 · junior à confirmé

    Vous lancez une tâche de fond avec tokio::spawn et la compilation échoue avec « borrowed value does not live long enough ». Pourquoi, et comment corrigez-vous ?

    Une bonne réponsetokio::spawn exige un future 'static et Send. La tâche peut vivre plus longtemps que la fonction qui la lance, donc elle ne peut pas emprunter une variable locale. On déplace la donnée dans la tâche avec async move, on clone un Arc pour l'état partagé, ou on communique par un canal. La personne explique l'erreur avant de proposer la correction.

    Signal d'alerteAjouter des 'static au hasard, ou faire fuir la mémoire avec Box::leak pour faire taire le compilateur.

Recevez la fiche d'entretien complète en PDF

La fiche pour mener l'entretien de bout en bout, prête à imprimer ou à partager avec le manager.

  • 30 questions sur 6 axes, avec la bonne réponse, le signal d'alerte et la relance
  • Le niveau visé pour chaque question
  • Une mise en situation de 20 minutes et ce qu'on attend du candidat
  • La grille d'évaluation de 1 à 4, axe par axe

Recrutement Rust back-end, les réponses aux questions courantes

Une expérience Rust en production est-elle indispensable ?

Pas forcément. Un bon développeur back-end Go, Java ou C++ qui a écrit du Rust sur des projets personnels ou open source progresse vite s'il est encadré par quelqu'un d'expérimenté. Exigez la production quand la personne sera seule sur le service ou devra fixer les conventions de l'équipe. Dans tous les cas, vérifiez l'async et l'accès aux données, deux sujets où les profils autodidactes ont souvent des lacunes.

Rust back-end et Rust embarqué, est-ce le même recrutement ?

Non. Les deux partagent le langage, ownership, traits et gestion d'erreurs. Le back-end ajoute Tokio, le HTTP, les bases de données, la sérialisation et le déploiement en conteneur. L'embarqué travaille souvent en no_std, sans allocateur, avec du code C existant et des contraintes de temps réel. Un profil embarqué peut réussir en back-end si l'async et les données sont vérifiés en entretien. Dans l'autre sens, le passage est plus difficile.

Quelles crates un développeur Rust back-end doit-il connaître ?

Le socle comprend Tokio, Serde, un framework HTTP (Axum ou Actix Web), une couche d'accès aux données (SQLx, Diesel ou SeaORM), thiserror et anyhow pour les erreurs, tracing pour les logs. Selon le service, ajoutez reqwest pour les appels sortants, tonic pour le gRPC, rdkafka ou async-nats pour les messages. La liste compte moins que la capacité à expliquer pourquoi une crate a été choisie plutôt qu'une autre.

Un test technique à faire chez soi convient-il pour un poste Rust ?

Un exercice court en entretien apprend souvent davantage qu'un projet de plusieurs jours. Donnez un petit service Axum qui contient deux défauts, un verrou tenu pendant un await et une erreur SQL renvoyée au client, puis demandez une revue de code commentée à voix haute. Un test technique à la maison peut servir de base, à condition d'en discuter ensuite : la façon d'expliquer les erreurs du compilateur compte autant que le code rendu.

Pour structurer vos autres entretiens, consultez aussi nos pages sur les questions d'entretien de recrutement, la méthode de l'entretien structuré et la grille d'évaluation d'entretien.

Les fiches voisines

Toutes les fiches →

Voyez Delectus sur vos propres offres.

Commencez gratuitement. Passez à l'échelle quand vous êtes prêt.

Demander une démo

Votre ATS, connecté à Delectus

  • Workable
  • Teamtailor
  • iCIMS
  • Lever
  • SmartRecruiters
  • Jobvite
  • Greenhouse
  • Pinpoint