Un seul package, deux entités : mythe ou réalité ?
\n\nImaginez : vous avez écrit en Rust une bibliothèque exceptionnelle pour le traitement de données massives. Rapide comme l'éclair, sécurisée et élégante. Mais vous avez besoin non seulement d'une API pour d'autres développeurs, mais aussi d'un utilitaire CLI prêt à l'emploi pour tester la bibliothèque ou l'utiliser dans des pipelines. Se pose alors la question classique qui a cumulé des milliers de vues sur Stack Overflow : « Package with both a library and a binary? » (un package contenant à la fois une bibliothèque et un fichier exécutable).
\n\nPour un débutant en Rust, cela ressemble à un casse-tête. Pour un développeur expérimenté, c'est un défi d'optimisation de la structure du projet et des performances. Dans cet article, nous ne nous contenterons pas de répondre à cette question ; nous le ferons en mettant l'accent sur l'optimisation et la performance — les deux choses pour lesquelles Rust est le plus apprécié.
\n\nNous verrons comment organiser correctement un tel package, éviter la duplication de code, accélérer la compilation et rendre votre binaire vraiment rapide. C'est parti !
\n\nStructure du package : Cargo.toml décide de tout
\n\nEn Rust, un seul package Cargo peut contenir à la fois une bibliothèque (lib) et plusieurs binaires (bin). Ce n'est pas de la magie, mais une simple convention de structure de dossiers. Regardons un exemple minimal.
\n\nCréons un projet :
\n\ncargo new my_tool --lib\n\nModifions maintenant le fichier Cargo.toml pour indiquer explicitement que nous avons à la fois une bibliothèque et un binaire :
[package]\nname = "my_tool"\nversion = "0.1.0"\nedition = "2021"\n\n[lib]\nname = "my_tool_lib"\npath = "src/lib.rs"\n\n[[bin]]\nname = "my_tool"\npath = "src/main.rs"\n\n[dependencies]\n\n\nLa structure des dossiers ressemblera à ceci :
\n\nmy_tool/\n├── Cargo.toml\n├── src/\n│ ├── lib.rs // Bibliothèque\n│ └── main.rs // Binaire\n\n\nTout simple, n'est-ce pas ? Mais le diable se cache dans les détails. Si vous copiez simplement le code de la bibliothèque dans main.rs, vous obtiendrez une duplication et des problèmes de performance. Il faut agir plus intelligemment.
\n\nLa bonne approche : le binaire utilise la bibliothèque
\n\nLe binaire doit importer la bibliothèque comme une crate externe. Même s'ils se trouvent dans le même package. Cela réduit non seulement la duplication, mais permet aussi au compilateur Rust de mieux optimiser le code grâce à l'optimisation inter-crate (LTO).
\n\nExemple de src/lib.rs :
pub fn calculate(data: &[u64]) -> u64 {\n data.iter().sum()\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_calculate() {\n assert_eq!(calculate(&[1, 2, 3]), 6);\n }\n}\n\n\nExemple de src/main.rs :
use my_tool_lib::calculate;\n\nfn main() {\n let numbers = vec![10, 20, 30];\n println!("Somme : {}", calculate(&numbers));\n}\n\n\nRemarquez : nous utilisons my_tool_lib — c'est le nom que nous avons donné à la bibliothèque dans Cargo.toml. Le compilateur les liera automatiquement en un seul binaire, tout en conservant la modularité.
Optimisation des performances : LTO, inline et benchmarks
\n\nMaintenant que la structure est prête, parlons de l'essentiel : comment tirer le maximum de votre package. Car si vous créez un outil de traitement de données, chaque microseconde compte.
\n\n1. Activez l'optimisation au moment de l'édition de liens (LTO)
\n\nLTO permet au compilateur d'optimiser le code à travers les limites des crates. Sans LTO, les fonctions de la bibliothèque et du binaire sont optimisées séparément. Avec LTO, elles le sont comme un seul ensemble.
\n\nAjoutez dans Cargo.toml :
[profile.release]\nlto = true\ncodegen-units = 1\n\n\nCela augmentera le temps de compilation, mais rendra le binaire nettement plus rapide. Pour un site éducatif comme pythonlib.ru, il est important de souligner : LTO est la norme pour les builds de production en Rust.
\n\n2. Utilisez les indications inline
\n\nSi la bibliothèque contient des fonctions critiques,