L’auteur, développeur Rust passionné par la propreté du code et l’unification des stacks techniques, raconte dans cet article comment il a remplacé React et JavaScript par Rust pour développer un SDK complet pour les Telegram WebApps (ou MiniApps). Frustré par la duplication des types entre son backend Rust et son frontend JavaScript, ainsi que par la gestion des dépendances npm, il a décidé d’expérimenter en enveloppant l’API officielle de Telegram dans une bibliothèque Rust compilée en WebAssembly via wasm-bindgen. Ce qui devait être un simple prototype s’est transformé en un projet de plus de 6 300 lignes de code, publié sur crates.io et déjà utilisé en production.

Le choix de Rust pour le frontend se justifie par plusieurs avantages clés. D’abord, la sécurité à la compilation : contrairement à JavaScript, où les erreurs de type ou les appels à des méthodes inexistantes ne sont détectées qu’à l’exécution, Rust bloque ces problèmes dès la compilation. Par exemple, passer une chaîne là où un nombre est attendu génère une erreur immédiate, évitant les classiques "undefined is not a function". Ensuite, l’unification du stack technique : les mêmes structures de données, validateurs et tests peuvent être partagés entre le frontend et le backend, éliminant la nécessité de maintenir des interfaces TypeScript synchronisées. La performance prévisible est un autre atout : WebAssembly, contrairement à JavaScript, n’a pas de pauses de garbage collection et offre une exécution déterministe, sans ralentissements imprévisibles liés à la réconciliation de frameworks comme React. Enfin, Rust évite les problèmes récurrents de l’écosystème npm (comme les dépendances supprimées ou modifiées arbitrairement), grâce à la politique d’immuabilité de crates.io, où les versions publiées ne peuvent pas être altérées.

Le SDK couvre intégralement l’API de Telegram WebApp (version 9.2), incluant la gestion des boutons principaux et secondaires, le thème et le viewport, le stockage cloud, l’authentification biométrique, les retours haptiques, les capteurs de l’appareil (accéléromètre, gyroscope), ou encore les paiements. L’architecture repose sur une couche de bindings générée par wasm-bindgen, qui expose une API Rust typée et sûre autour des méthodes JavaScript originales. Des modules dédiés organisent les fonctionnalités (comme main_button ou cloud_storage), et des intégrations sont proposées pour les frameworks frontend Rust comme Yew (inspiré de React) et Leptos (proche de Solid.js). Pour faciliter le développement local, un environnement de mock simule l’API de Telegram, permettant de tester l’application dans le navigateur sans avoir à la déployer dans Telegram — une économie de temps considérable.

Le projet n’a cependant pas été sans défis. Le plus critique a concerné la gestion des événements : en JavaScript, ajouter un écouteur est trivial, mais en Rust, les fermetures (closures) passées à wasm-bindgen doivent avoir une durée de vie 'static pour éviter des segmentation faults. La solution a nécessité trois jours de débogage intensif et l’implémentation d’une structure EventHandle utilisant Box::leak pour gérer manuellement la mémoire. Ce genre de problème, bien que complexe, illustre la robustesse de Rust : une fois résolu, le bug est éliminé définitivement, contrairement à JavaScript où des erreurs similaires pourraient réapparaître en production sans explication claire. D’autres limitations subsistent, comme des sourcemaps moins précises qu’en JavaScript, rendant le débogage plus ardent, ou l’absence de Hot Module Replacement (HMR), qui oblige à recompiler entièrement le WASM (5 à 10 secondes) contre des mises à jour instantanées avec Vite ou Webpack. Enfin, la taille du binaire WASM reste supérieure à un bundle JavaScript optimisé (environ 50 Ko contre 30 Ko pour React), bien que cette différence s’atténue pour des applications plus volumineuses.

Pour illustrer la différence d’approche, l’auteur compare un exemple concret : la configuration d’un bouton de paiement. En JavaScript, le code est concis mais vulnérable aux erreurs de typage ou aux valeurs null non gérées, qui ne seront détectées qu’à l’exécution. En Rust, chaque paramètre est vérifié à la compilation, et les structures comme MainButtonParams garantissent que tous les champs requis sont présents et typés correctement. Les macros comme telegram_app! réduisent également la verbosité du code en automatisant l’initialisation du SDK et la configuration du mock en mode développement. Malgré ces avantages, l’auteur reconnaît que Rust n’est pas une solution miracle pour tous les projets frontend, notamment à cause de son courbe d’apprentissage et de ses outils moins matures que ceux de l’écosystème JavaScript. Néanmoins, pour des applications où la fiabilité, la performance et la cohérence du code sont critiques, cette approche offre une alternative convaincante aux frameworks traditionnels.