URL: https://linuxfr.org/users/jeanas/journaux/mon-inquietude-sur-les-dependances-en-rust Title: Mon inquiétude sur les dépendances en Rust Authors: jeanas Date: 2024年07月02日T01:16:22+02:00 License: CC By-SA Tags: dépendances, rust et vulnérabilité Score: 83 Bonjour 'Nal, Tout a commencé quand j'ai voulu faire des choses un peu inhabituelles sur mon blog personnel (entre autres, écrire des maths avec une syntaxe personnalisée), et pour cela changer le moteur de blog. De [Zola](https://www.getzola.org/), un générateur de site statique dans la lignée de Jekyll, Hugo, etc., je suis passé à un petit script maison. J'ai décidé de l'écrire en Rust, car même si ce n'est pas le plus pratique comme langage de script, c'est un langage que j'adore et dans lequel je voulais justement acquérir plus d'aisance. Comme la structure du site proposée par Zola me paraissait plutôt pratique, je l'ai plus ou moins conservée. Il y a des fichiers Markdown convertis en HTML, avec un en-tête en TOML pour les métadonnées (date, etc.) ; des templates (en l'occurrence en [Tera](https://keats.github.io/tera/)) qui définissent le HTML autour du Markdown rendu et peuvent aussi constituer des pages à part entière ; et des fichiers quelconques qui sont simplement copiés (images, CSS, etc.). Je commence donc à écrire mon script, un peu laborieusement car je n'ai pas touché à Rust depuis un moment. J'ajoute au fur et à mesure les dépendances dont j'ai besoin, en prenant des librairies parmi les plus courantes : - [anyhow](https://lib.rs/crates/anyhow) pour propager facilement les erreurs, - [clap](https://lib.rs/crates/clap) pour le parsing des arguments en ligne de commande du script, - [heck](https://lib.rs/crates/heck) pour convertir entre différents styles de nommage, - [pulldown-cmark](https://lib.rs/crates/pulldown-cmark) pour lire le Markdown et le convertir en HTML, - [regex](https://lib.rs/crates/regex) pour les expressions régulières, et [lazy_static](https://lib.rs/crates/lazy_static) pour ne compiler une regex qu'une fois, - [tera](https://lib.rs/crates/tera) pour les templates (même engin que Zola), - [toml](https://lib.rs/crates/toml) pour lire le TOML, - [walkdir](https://lib.rs/crates/walkdir) pour lister un dossier récursivement. Comme j'ai plutôt l'habitude d'écrire des scripts en Python, j'ai tendance à chercher « la librairie Rust qui correspond à tel module Python ». Par exemple, j'ai ajouté `regex` sans me poser trop de questions (en pensant à `import re`), et de même pour `clap` (`import argparse`) et `walkdir` (`from os import walk`). À un moment donné, j'ai l'idée de regarder les dépendances transitives. Et là, surprise en examinant le `Cargo.lock` : il y en a **126**. 9 dépendances directes, ce n'est déjà pas rien, mais 126 dépendances au total, je trouve ça complètement excessif. Je sais qu'il y a des projets à 500 dépendances, en particulier en JavaScript, je sais qu'il y en a que ça ne dérange pas, mais je sais aussi ce qui est arrivé à left-pad, [à polyfill.io](https://linuxfr.org/users/glandos/journaux/polyfill-io-est-contamine), à xz, et d'autres, et je ne veux certainement pas passer le temps qu'il faudrait pour vérifier ne serait-ce que superficiellement qui sont les auteurs de toutes ces librairies et si je suis prêt à leur faire confiance. Je ne suis pas non plus en train d'écrire l'application Web d'une entreprise, mais un bête script générateur de blog. Pour le dire de manière plus éloquente, certes à un point où mon script ne marchait pas encore, j'avais *plus de dépendances que de lignes de code*. En examinant `cargo tree`, j'ai pu constater quelles librairies contribuaient le plus à cette explosion. D'abord, `clap` : ``` └── clap v4.5.8 └── clap_builder v4.5.8 ├── anstream v0.6.14 │ ├── anstyle v1.0.7 │ ├── anstyle-parse v0.2.4 │ │ └── utf8parse v0.2.2 │ ├── anstyle-query v1.1.0 │ ├── colorchoice v1.0.1 │ ├── is_terminal_polyfill v1.70.0 │ └── utf8parse v0.2.2 ├── anstyle v1.0.7 ├── clap_lex v0.7.1 └── strsim v0.11.1 ``` Aussitôt remplacée par [pico-args](https://lib.rs/crates/pico-args), qui n'est pas tout à fait aussi pratique mais n'a aucune dépendance et ne fait même pas 700 lignes de code. (En plus, je fais confiance à l'auteur, qui a aussi écrit une excellente librairie de rendu SVG, [resvg](https://lib.rs/crates/resvg).) Ensuite, il y a `tera` : ``` └── tera v1.20.0 ├── chrono v0.4.38 │ ├── iana-time-zone v0.1.60 │ └── num-traits v0.2.19 │ [build-dependencies] │ └── autocfg v1.3.0 ├── chrono-tz v0.9.0 │ ├── chrono v0.4.38 (*) │ └── phf v0.11.2 │ └── phf_shared v0.11.2 │ └── siphasher v0.3.11 │ [build-dependencies] │ └── chrono-tz-build v0.3.0 │ ├── parse-zoneinfo v0.3.1 │ │ └── regex v1.10.5 │ │ ├── regex-automata v0.4.7 │ │ │ └── regex-syntax v0.8.4 │ │ └── regex-syntax v0.8.4 │ ├── phf v0.11.2 (*) │ └── phf_codegen v0.11.2 │ ├── phf_generator v0.11.2 │ │ ├── phf_shared v0.11.2 (*) │ │ └── rand v0.8.5 │ │ └── rand_core v0.6.4 │ └── phf_shared v0.11.2 (*) ├── globwalk v0.9.1 │ ├── bitflags v2.6.0 │ ├── ignore v0.4.22 │ │ ├── crossbeam-deque v0.8.5 │ │ │ ├── crossbeam-epoch v0.9.18 │ │ │ │ └── crossbeam-utils v0.8.20 │ │ │ └── crossbeam-utils v0.8.20 │ │ ├── globset v0.4.14 │ │ │ ├── aho-corasick v1.1.3 │ │ │ │ └── memchr v2.7.4 │ │ │ ├── bstr v1.9.1 │ │ │ │ └── memchr v2.7.4 │ │ │ ├── log v0.4.22 │ │ │ ├── regex-automata v0.4.7 │ │ │ │ ├── aho-corasick v1.1.3 (*) │ │ │ │ ├── memchr v2.7.4 │ │ │ │ └── regex-syntax v0.8.4 │ │ │ └── regex-syntax v0.8.4 │ │ ├── log v0.4.22 │ │ ├── memchr v2.7.4 │ │ ├── regex-automata v0.4.7 (*) │ │ ├── same-file v1.0.6 │ │ └── walkdir v2.5.0 │ │ └── same-file v1.0.6 │ └── walkdir v2.5.0 (*) ├── humansize v2.1.3 │ └── libm v0.2.8 ├── lazy_static v1.5.0 ├── percent-encoding v2.3.1 ├── pest v2.7.10 │ ├── memchr v2.7.4 │ ├── thiserror v1.0.61 │ │ └── thiserror-impl v1.0.61 (proc-macro) │ │ ├── proc-macro2 v1.0.86 │ │ │ └── unicode-ident v1.0.12 │ │ ├── quote v1.0.36 │ │ │ └── proc-macro2 v1.0.86 (*) │ │ └── syn v2.0.68 │ │ ├── proc-macro2 v1.0.86 (*) │ │ ├── quote v1.0.36 (*) │ │ └── unicode-ident v1.0.12 │ └── ucd-trie v0.1.6 ├── pest_derive v2.7.10 (proc-macro) │ ├── pest v2.7.10 (*) │ └── pest_generator v2.7.10 │ ├── pest v2.7.10 (*) │ ├── pest_meta v2.7.10 │ │ ├── once_cell v1.19.0 │ │ └── pest v2.7.10 (*) │ │ [build-dependencies] │ │ └── sha2 v0.10.8 │ │ ├── cfg-if v1.0.0 │ │ ├── cpufeatures v0.2.12 │ │ └── digest v0.10.7 │ │ ├── block-buffer v0.10.4 │ │ │ └── generic-array v0.14.7 │ │ │ └── typenum v1.17.0 │ │ │ [build-dependencies] │ │ │ └── version_check v0.9.4 │ │ └── crypto-common v0.1.6 │ │ ├── generic-array v0.14.7 (*) │ │ └── typenum v1.17.0 │ ├── proc-macro2 v1.0.86 (*) │ ├── quote v1.0.36 (*) │ └── syn v2.0.68 (*) ├── rand v0.8.5 │ ├── libc v0.2.155 │ ├── rand_chacha v0.3.1 │ │ ├── ppv-lite86 v0.2.17 │ │ └── rand_core v0.6.4 │ │ └── getrandom v0.2.15 │ │ ├── cfg-if v1.0.0 │ │ └── libc v0.2.155 │ └── rand_core v0.6.4 (*) ├── regex v1.10.5 │ ├── aho-corasick v1.1.3 (*) │ ├── memchr v2.7.4 │ ├── regex-automata v0.4.7 (*) │ └── regex-syntax v0.8.4 ├── serde v1.0.203 ├── serde_json v1.0.120 │ ├── itoa v1.0.11 │ ├── ryu v1.0.18 │ └── serde v1.0.203 ├── slug v0.1.5 │ └── deunicode v1.6.0 └── unic-segment v0.9.0 └── unic-ucd-segment v0.9.0 ├── unic-char-property v0.9.0 │ └── unic-char-range v0.9.0 ├── unic-char-range v0.9.0 └── unic-ucd-version v0.9.0 └── unic-common v0.9.0 ``` Sérieusement, c'est juste effrayant. Je me suis tourné vers [Minijinja](https://lib.rs/crates/minijinja), qui fait plus ou moins la même chose avec une seule dépendance obligatoire — et qui en fait un but explicite (« Minijinja is a powerful but minimal dependency template engine »), l'auteur, un [développeur connu](https://en.wikipedia.org/wiki/Armin_Ronacher), ayant [écrit](https://lucumr.pocoo.org/2016/3/24/open-source-trust-scaling/) [sa peur](https://lucumr.pocoo.org/2019/7/29/dependency-scaling/) face à l'explosion des micro-dépendances. Au départ, j'ai utilisé une fonctionnalité optionnelle de Minijinja qui rajoute deux dépendances, pour avoir [une certaine API](https://docs.rs/minijinja/latest/minijinja/struct.Environment.html#method.add_template_owned), puis j'ai refactorisé mon code pour ne pas en avoir besoin. Ensuite, il y a le cas de `regex` : ``` └── regex v1.10.5 ├── aho-corasick v1.1.3 │ └── memchr v2.7.4 ├── memchr v2.7.4 ├── regex-automata v0.4.7 │ ├── aho-corasick v1.1.3 (*) │ ├── memchr v2.7.4 │ └── regex-syntax v0.8.4 └── regex-syntax v0.8.4 ``` Contrairement à Tera, ça ne me fait pas hurler, surtout que toutes ces librairies sont du même auteur — [Andrew Gallant](https://blog.burntsushi.net/) — et essentiellement crées pour les besoins de `regex`. Je vois un peu qui est Andrew Gallant, il est connu comme étant l'auteur de [ripgrep](https://github.com/BurntSushi/ripgrep), et `regex` est une merveille de performance qui fait justement le succès de ripgrep. D'un autre côté, j'avais ajouté la dépendance à `regex` par réflexe pythonicien, mais je n'en avais besoin que pour une seule expression régulière toute bête, que j'ai facilement remplacée par deux appels à [`str::split_once`](https://doc.rust-lang.org/std/primitive.str.html#method.split_once). Du même coup, j'ai éliminé la dépendance à `lazy-static`, dont j'ai appris par ailleurs qu'elle se remplaçait facilement par [`OnceCell`](https://doc.rust-lang.org/std/cell/struct.OnceCell.html) qui est dans la bibliothèque standard. Il y avait encore `walkdir` : ``` └── walkdir v2.5.0 └── same-file v1.0.6 ``` (en fait, quelques dépendances de plus sous Windows). D'un côté, ce n'est pas monstrueux (et c'est encore du Andrew Gallant), d'un autre côté, ce n'est tout de même pas bien compliqué d'implémenter la lecture récursive d'un dossier. Je me suis empressé de le faire, en 50 lignes de code, et de retirer cette dépendance. J'ai aussi remplacé `heck` par un bête `.replace('-', "_")` après m'être assuré que cela convenait dans mon cas. Bilan : je n'ai « plus que » 24 dépendances, pour 5 dépendances directes : `anyhow`, `minijinja`, `pico-args`, `pulldown-cmark` et `toml`. L'arbre est celui-ci : ``` ├── anyhow v1.0.86 ├── minijinja v2.0.2 │ └── serde v1.0.203 ├── pico-args v0.5.0 ├── pulldown-cmark v0.11.0 │ ├── bitflags v2.5.0 │ ├── memchr v2.7.4 │ ├── pulldown-cmark-escape v0.11.0 │ └── unicase v2.7.0 │ [build-dependencies] │ └── version_check v0.9.4 └── toml v0.8.14 ├── serde v1.0.203 ├── serde_spanned v0.6.6 │ └── serde v1.0.203 ├── toml_datetime v0.6.6 │ └── serde v1.0.203 └── toml_edit v0.22.14 ├── indexmap v2.2.6 │ ├── equivalent v1.0.1 │ └── hashbrown v0.14.5 ├── serde v1.0.203 ├── serde_spanned v0.6.6 (*) ├── toml_datetime v0.6.6 (*) └── winnow v0.6.13 ``` Les deux plus gros contributeurs sont `pulldown-cmark` et `toml`. Ces deux bibliothèques sont utilisées respectivement par `rustdoc` (l'outil standard de documentation d'API en Rust, utilisé par absolument tout le monde) et par Cargo lui-même, donc j'ai plutôt confiance en ces deux librairies. Quelle est la conclusion ? Que ce n'est pas bien difficile, dans beaucoup de cas, de se passer d'une dépendance. J'ai l'impression qu'il y a une culture, dans une partie du monde informatique, de prendre les dépendances trop à la légère. J'ai pris Rust comme exemple, mais je crois comprendre que JavaScript est encore pire. Que les outils comme Cargo rendent très facile l'utilisation de librairies, c'est fantastique (en Python c'est [plus compliqué](https://linuxfr.org/news/l-installation-et-la-distribution-de-paquets-python-2-4), et ne parlons même pas de C ou C++), mais il ne faut pas abuser des bonnes choses. Chaque dépendance a un coût, celui de la mettre à jour et de s'adapter si elle change son API, et elle porte un risque, celui d'être utilisée pour propager du code malveillant, ou tout simplement d'avoir des vulnérabilité (à force d'importer de plus en plus de code dans son projet, on augmente du même coup la surface d'attaque potentielle). Quand on a 130 dépendances, il est irréaliste de les auditer individuellement. Les outils comme [`cargo audit`](https://lib.rs/crates/cargo-audit) sont une très bonne chose, mais si on peut s'en passer, c'est encore mieux.

AltStyle によって変換されたページ (->オリジナル) /