Calculer ce fichier, c'est le boulot de mon moteur d'entrelacement. Il est écrit en Rust, et chaque planche A4 qu'il produit l'oblige à jongler avec la résolution de l'imprimante (souvent différente à l'horizontale et à la verticale), l'écartement des lentilles, la correction de perspective et les sous-pixels rouges, verts et bleus, sur des dizaines de millions de pixels.
En le construisant, j'ai dû réfléchir sérieusement aux performances, et à une phrase que tout le monde répète sans jamais l'expliquer : « Rust, c'est rapide. » Rapide par rapport à quoi ? Rapide grâce à quoi ? Et est-ce le langage, ou simplement le fait d'avoir enfin écrit quelque chose avec soin ?
Cet article est donc ma tentative de réponse honnête à une question simple : Rust est-il vraiment plus rapide pour manipuler des images ? La version courte : « pas par magie, mais d'une façon qui compte ». La version longue est ci-dessous, et vous n'avez pas besoin de connaître Rust pour la suivre.
Pourquoi le code image est un problème difficile
Une image, c'est juste une grande grille de nombres. Une photo de téléphone compte environ 12 millions de pixels, et chaque pixel a généralement trois ou quatre valeurs (rouge, vert, bleu, et parfois la transparence). Chaque opération, qu'il s'agisse de redimensionner, d'accentuer la netteté ou de passer en niveaux de gris, revient à exécuter un petit calcul des dizaines de millions de fois.
Ça a deux conséquences.
D'abord, les petits coûts s'additionnent. Si votre code perd quelques nanosecondes par pixel, personne ne le remarque sur un seul pixel. Sur 40 millions, si. C'est pour ça que les bibliothèques d'images sont traditionnellement écrites en C ou en C++, des langages qui permettent de contrôler précisément ce que fait la machine.
Ensuite, les fichiers images viennent d'inconnus. Votre navigateur décode chaque PNG et chaque JPEG de chaque page visitée, et ces fichiers peuvent être volontairement malformés pour piéger le décodeur. Le C offre la vitesse, mais il permet aussi à une simple erreur d'un octet de devenir une faille de sécurité : lire au-delà de la fin d'un tampon, écrire dans une mémoire qui ne vous appartient pas, etc. Les décodeurs d'images ont un long historique de ce genre de bugs.
Pendant longtemps, le choix ressemblait donc à ça :
C ou C++ : rapide, mais une erreur peut devenir une vulnérabilité.
Python, JavaScript, Go et compagnie : plus sûr, mais on finit généralement par appeler une bibliothèque C pour le gros du travail.
La promesse de Rust, c'est qu'on n'a plus à choisir. Voyons si ça tient la route.
D'où vient vraiment la vitesse
Voici la partie que la plupart des articles « Rust est rapide » sautent. Rust n'a pas de bouton secret pour aller plus vite. Un développeur C soigneux et un développeur Rust soigneux atteignent en général à peu près les mêmes performances. Ce que Rust change, c'est à quel point il est sûr d'aller chercher ces performances. Pour les images, quelques ingrédients comptent :
Pas de ramasse-miettes. Des langages comme Go, Java ou JavaScript s'arrêtent régulièrement pour nettoyer la mémoire inutilisée. Rust détermine dès la compilation quand la mémoire peut être libérée, donc rien ne tourne en arrière-plan. Pour une boucle serrée sur des pixels, ça veut dire des temps d'exécution prévisibles.
SIMD : une instruction, plein de pixels. Les processeurs modernes peuvent appliquer la même opération à un lot de valeurs d'un coup, par exemple 16 ou 32 octets en une seule instruction. Pour des images, où l'on fait la même chose sur chaque pixel, c'est un gain énorme. C'est aussi, traditionnellement, là que ça devient risqué, car utiliser ces instructions directement impliquait d'écrire du code que le compilateur ne peut pas vérifier. Ça change : l'équipe qui a conçu le nouveau décodeur JPEG XL de Chrome explique qu'une fonctionnalité de Rust récemment stabilisée leur permet d'utiliser le SIMD sans marquer le code comme unsafe, et qu'ils ont cantonné le reste du code non sûr à un petit nombre d'endroits soigneusement relus.[^1]
Un multithreading facile. Votre ordinateur a probablement 8 cœurs ou plus. Répartir le travail sur tous ces cœurs est souvent la plus grosse accélération possible, et en Rust ça peut tenir en une ligne grâce à une bibliothèque appelée Rayon :
use rayon::prelude::*;
// Un seul cœur, une ligne à la fois
rows.iter_mut().for_each(|row| process(row));
// Tous les cœurs, lignes distribuées automatiquement
rows.par_iter_mut().for_each(|row| process(row));
Le borrow checker rend les techniques risquées sûres à essayer. C'est ça, le vrai secret. Le compilateur de Rust suit qui a le droit de lire ou de modifier chaque morceau de mémoire. Si deux threads pouvaient un jour écrire le même pixel en même temps (une « data race »), le code ne compile tout simplement pas. En C, ce bug compile sans broncher et apparaît sous forme de défaut bizarre sur une image sur mille. En Rust, vous le découvrez avant même de lancer le programme.
Mettez tout ça ensemble et vous obtenez la vraie réponse à « pourquoi Rust est rapide pour les images » : il permet d'utiliser toutes les astuces de vitesse disponibles pendant que le compilateur rattrape les erreurs que ces astuces provoquent d'habitude. 🦀
Pièce à conviction A : les navigateurs changent de camp
Pour savoir si quelque chose est à la fois rapide et sûr, regardez ce que font les équipes des navigateurs. Elles sont obsédées par la vitesse (chaque chargement de page compte) et par la sécurité (chaque image est une attaque potentielle).
Fin 2024, les mainteneurs de plusieurs bibliothèques PNG en Rust ont publié des benchmarks montrant que les décodeurs PNG sûrs en mémoire avaient dépassé les décodeurs C classiques. Le meilleur, la crate Rust png, décodait les images environ 1,8 fois plus vite que libpng sur x86 et 1,5 fois plus vite sur ARM. Une partie de ce gain dépend encore d'optimisations SIMD propres à chaque processeur, donc les résultats varient selon le matériel.[^2]
Chromium, la base open source de Chrome et d'Edge, a ensuite suivi. Une modification récente retire libpng de Skia, le moteur graphique de Chromium, après avoir basculé l'encodage et le décodage PNG sur l'implémentation Rust.[^3] libpng est la façon standard de lire des PNG depuis une trentaine d'années, donc ce n'est pas rien.
Et en octobre 2026, Chrome 155 a commencé à prendre en charge JPEG XL grâce à jxl-rs, un décodeur entièrement écrit en Rust. Le raisonnement de l'équipe résume parfaitement le sujet : les décodeurs d'images font partie des composants les plus attaqués d'un navigateur, et un décodeur sûr en mémoire qui tient la cadence du meilleur décodeur non sûr est un choix évident. Ils indiquent aussi que, malgré un fuzzing intensif (des millions de fichiers corrompus aléatoires envoyés au décodeur), ils n'ont trouvé aucun bug de sécurité mémoire de toute son histoire.[^1]
Les gens qui ont le plus à perdre, côté vitesse comme côté sécurité, choisissent donc Rust. C'est un signal fort, mais ce n'est pas toute l'histoire.
Pièce à conviction B : le redimensionnement, et un retour à la réalité 🔍
Décoder, c'est une chose. Et pour réellement manipuler des images ?
Le redimensionnement est un bon test, car presque toutes les applis en font. La crate fast_image_resize publie des benchmarks face à d'autres bibliothèques, dont libvips, une bibliothèque C réputée très rapide. Voici ses chiffres pour réduire une photo de 4928×3279 à 852×567 avec un filtre bilinéaire, sur un seul cœur :[^4]
image (la crate Rust généraliste) : environ 78 ms
resize (une autre crate Rust) : environ 48 ms
fast_image_resize, Rust simple : environ 16 ms
libvips (C) : environ 5,7 ms
fast_image_resize avec SIMD SSE4.1 : environ 5,8 ms
fast_image_resize avec SIMD AVX2 : environ 3,5 ms
Relisez cette liste, car c'est la donnée la plus honnête de cet article. La version Rust simple est environ trois fois plus lente que libvips. Elle ne passe devant qu'une fois qu'elle utilise les instructions SIMD du processeur. Ce n'est pas le fait d'être écrite en Rust qui l'a rendue rapide. C'est le travail sur le SIMD, et Rust a rendu ce travail plus sûr.
On voit aussi l'effet inverse : deux bibliothèques Rust sur la même tâche peuvent avoir un écart de 1 à 20. Le langage fixe le plafond, pas le résultat.
La bibliothèque de vision par ordinateur kornia-rs raconte une histoire similaire sous un autre angle. Ses auteurs annoncent être 3 à 5 fois plus rapides que la crate image sur les retournements, redimensionnements et conversions en niveaux de gris, avec un écart qui grandit à mesure que les images grossissent. Ils l'attribuent à des choix de conception comme la réutilisation de mémoire préallouée et le travail sur des « vues » d'une image plutôt que sur des copies.[^5]
Une réserve sur ces deux exemples : ce sont des benchmarks publiés par les auteurs des bibliothèques eux-mêmes. Ils sont utiles et reproductibles, mais ce ne sont pas des tests indépendants. Voyez-les comme « voilà ce qui est possible », pas « voilà ce que vous obtiendrez ».
Étude de cas : mon moteur d'entrelacement lenticulaire
C'est ici que je peux parler de mon propre travail.
Vous avez sûrement déjà vu des impressions lenticulaires : ces cartes ou affiches qui changent d'image quand on les incline. L'astuce, c'est une feuille de minuscules lentilles striées posée sur une image spécialement préparée. Cette image est construite en découpant deux visuels ou plus en bandes très fines et en les alternant, de sorte que sous un certain angle les lentilles montrent les bandes de l'image A, et sous un autre celles de l'image B. Cette étape de découpage et d'alternance s'appelle l'entrelacement, et c'est ce que fait mon moteur pour les effets flip.
Ça a l'air simple, mais c'est un exemple parfait de boucle critique pixel par pixel. L'écartement des lentilles ne tombe presque jamais pile sur la grille de pixels de l'imprimante, l'imprimante a souvent des résolutions différentes à l'horizontale et à la verticale, et pour un résultat net on finit par travailler au niveau des sous-pixels rouges, verts et bleus. Pour chaque pixel d'une page A4 en résolution d'impression (des dizaines de millions), le moteur doit déterminer de quelle image source et de quelle colonne exacte il doit venir, puis corriger des choses comme une légère déformation ou le « ghosting » entre les images.
Ce que Rust a rendu facile :
Découper la page entre les cœurs sans crainte. Chaque ligne de sortie (ou bande de lignes) peut être calculée indépendamment, donc la page peut être découpée en morceaux traités en parallèle. Rust donne à chaque thread sa propre portion de la sortie, sans chevauchement, et le compilateur garantit que deux threads n'écrivent jamais au même endroit :
use rayon::prelude::*;
output
.par_chunks_mut(row_len) // une portion modifiable par ligne
.enumerate()
.for_each(|(y, row)| interlace_row(y, row, &frames, ¶ms));
Lire les images sources en toute sécurité. Toute cette géométrie implique beaucoup d'indices calculés dans les images sources. En C, une erreur d'arrondi au bord de la page lit de la mémoire au hasard. En Rust, elle est soit détectée par le compilateur, soit arrêtée avec une erreur claire, au lieu d'imprimer discrètement une carte corrompue.
Ce que Rust vous coûte
Une réponse équilibrée doit inclure la facture. Rust n'est pas gratuit, et pour beaucoup de travaux sur les images, ce n'est pas le bon choix.
La courbe d'apprentissage est réelle. Le borrow checker qui attrape tous ces bugs rejettera aussi pas mal de code qui aurait très bien fonctionné. Attendez-vous à quelques premières semaines frustrantes.
La compilation est lente. Les gros projets Rust mettent nettement plus de temps à compiler que des projets Go ou C, ce qui ralentit la boucle modifier-lancer-vérifier.
Les builds de debug peuvent être très lents. Le code image qui s'appuie sur le SIMD peut tourner bien plus lentement dans le mode debug par défaut de Rust, assez pour rendre les tests avec de vraies images pénibles. La solution habituelle consiste à demander à Cargo d'optimiser vos dépendances même en debug :
# Cargo.toml
[profile.dev.package."*"]
opt-level = 3
Il reste un peu d'**unsafe** aux extrémités. Parler directement à certaines fonctionnalités matérielles l'exige parfois. La bonne nouvelle, c'est qu'il est limité à de petits endroits clairement signalés qui font l'objet d'une relecture renforcée, au lieu d'être éparpillé partout.
Les bibliothèques C matures restent excellentes. Comme l'ont montré les chiffres de redimensionnement, libvips tient tête à du Rust bien optimisé. Si vous avez juste besoin de miniatures dans une appli web, appeler libvips (ou une surcouche comme Sharp en Node.js) est souvent le choix pragmatique.
Pas besoin de tout réécrire. Si vous travaillez en Python ou en JavaScript, le moyen le plus pratique de profiter de Rust est souvent d'appeler une bibliothèque Rust depuis votre code existant, ou de compiler du Rust en WebAssembly pour le navigateur, plutôt que de changer de langage.
À retenir
Rust n'est pas magiquement plus rapide. Une boucle Rust simple peut perdre face à une bibliothèque C bien optimisée. La vitesse vient de techniques comme le SIMD et le multithreading.
Ce que Rust change, c'est la sécurité pendant ce travail. Le compilateur attrape les bugs mémoire et les data races qui rendent le code image rapide dangereux en C.
L'industrie vote avec son code. Les décodeurs PNG en Rust battent désormais libpng, Chromium a abandonné libpng, et Chrome embarque un décodeur JPEG XL en Rust.
Le choix de la bibliothèque compte autant que le langage. Deux bibliothèques Rust peuvent avoir un écart de 1 à 20 sur la même tâche.
Ça se paie en temps d'apprentissage et de compilation. Pour du travail lourd et sur mesure, pixel par pixel, comme mon moteur d'entrelacement, le compromis en valait la peine. Pour de simples miniatures, une bibliothèque existante vous servira peut-être mieux.
[^1]: Luca Versari, Moritz Firsching, Philip Jägenstedt. "Shipping JPEG XL in Chrome." developer.chrome.com. https://developer.chrome.com/blog/jpeg-xl-in-chrome
[^2]: Michael Larabel. "Rust-Based, Memory-Safe PNG Decoders "Vastly Outperform" C-Based PNG Libraries." phoronix.com. https://www.phoronix.com/news/Rust-PNG-Outperforms-C-PNG
[^3]: Łukasz Anforowicz. "[rust png] Remove Chromium's //skia => libpng dependency." chromium.googlesource.com. https://chromium.googlesource.com/chromium/src/+/32b26f051b8dc7c2ff94f0e207b718761eece3e6
[^4]: Cykooz. "fast_image_resize." github.com. https://github.com/cykooz/fast_image_resize
[^5]: Edgar Riba et al. "Kornia-rs: A Low-Level 3D Computer Vision Library In Rust." arxiv.org. https://arxiv.org/pdf/2505.12425



