Introduction : Le fantôme des binaires géants
\n\nVous avez écrit un programme en Rust. Seulement 200 lignes de code, un simple utilitaire CLI pour analyser des logs. Vous lancez cargo build --release et, le cœur battant, vous regardez la taille du fichier dans le dossier target/release. 5… 10… 20 mégaoctets ? Pour une application aussi simple ? Une douleur familière, n'est-ce pas ?
La question « Why are Rust executables so huge? » sur Stack Overflow a rassemblé des milliers de vues et des centaines de commentaires. En effet, comparez : un programme analogue en C ou Go pèse souvent 1–2 Mo, alors qu'en Rust, il en fait 15. Où est le problème ? Rust est un langage système qui se veut efficace, d'où vient ce poids supplémentaire ?
\n\nDans cet article, nous allons analyser honnêtement l'anatomie d'un gros binaire Rust, démystifier les idées reçues et, surtout, montrer des méthodes éprouvées pour réduire la taille de votre application de 2 à 5 fois sans perte de performance. Tous les exemples sont valables pour Rust 2024–2025.
\n\nPourquoi les programmes Rust sont-ils si gros ? Anatomie d'un binaire
\n\nAvant de paniquer, regardons à l'intérieur de notre main.exe. Qu'est-ce qui prend exactement de la place ?
1. L'édition de liens statique de la bibliothèque standard
\n\nContrairement à Go, qui lie le runtime de manière statique, ou à C, qui utilise souvent des bibliothèques partagées (DLL/so), Rust par défaut lie std de manière statique. Cela signifie que toute la bibliothèque standard, y compris l'allocateur, les traits, les collections, etc., est intégrée directement dans votre binaire. Avantage : la portabilité, pas besoin de transporter des DLL. Inconvénient : la taille.
Même le plus simple « Hello, World! » en Rust pèse environ 300–400 Ko (en release). Beaucoup ? En réalité, c'est déjà optimisé : les premières versions de Rust produisaient 1–2 Mo pour le même code.
\n\n2. La monomorphisation des génériques
\n\nRust est un langage avec monomorphisation des génériques. Cela signifie que pour chaque type concret avec lequel une fonction générique est appelée, le compilateur génère une copie distincte. Vec<u32> et Vec<String> sont deux ensembles d'instructions différents. Cela offre des performances maximales, mais augmente la taille.
3. Symboles de débogage et panique
\n\nMême dans une compilation release, Rust laisse par défaut certaines informations pour le traçage des piles de panique et les backtraces. Ce ne sont pas seulement des chaînes de caractères, mais des tables de symboles entières qui occupent des mégaoctets.
\n\n4. L'allocateur par défaut
\n\nRust utilise-t-il jemalloc comme allocateur par défaut ? Non, depuis Rust 1.32, l'allocateur par défaut est celui du système (glibc sur Linux, malloc sur Windows). Cependant, si vous utilisez jemalloc via une crate, son code est également lié statiquement et ajoute 1–3 Mo.
Mythes sur la taille des binaires Rust
\n\n- \n
- Mythe : « Rust crée toujours des binaires géants, c'est sa malédiction ». Réalité : Le Rust moderne (1.80+) avec les bons flags peut créer des binaires de taille comparable à ceux du C. \n
- Mythe : « Il faut utiliser
#![no_std]pour les petits binaires ». Réalité : Pour l'embarqué, oui, mais pour les applications classiques, on peut obtenir d'excellents résultats sans abandonner la bibliothèque standard. \n - Mythe : « Un gros binaire = mauvaise performance ». Réalité : La taille et la vitesse sont des choses différentes. Un gros binaire peut être très rapide. \n
Meilleures pratiques 2025 : Comment réduire un binaire Rust
\n\nPassons maintenant à la partie pratique. Voici des techniques éprouvées qui fonctionnent en 2025. Appliquez-les en fonction de vos objectifs : optimisation maximale pour la production ou taille minimale pour l'embarqué.
\n\nMéthode 1 : Un profil correct dans Cargo.toml
\n\nL'étape la plus simple et la plus efficace est de configurer le profil de release. Ajoutez dans Cargo.toml :
[profile.release]\nopt-level = "z" # Optimisation