← Retour aux insights← Back to insights

Pourquoi nous écrivons encore du C en 2025. Why we still write C in 2025.

Sur les microcontrôleurs sous 64 Ko de RAM, le choix du langage n'est pas une opinion. C'est une contrainte physique. On microcontrollers below 64 KB of RAM, language choice is not an opinion. It's a physical constraint.

C est un vieux langage. Il est dangereux. Il ne protège de rien. On peut écrire du code qui compile, passe la revue, et corrompt la mémoire en production trois mois plus tard. Tout ce que la critique dit du C est exact.

Et pourtant, en 2025, nous l'utilisons encore dans la majorité de nos projets embarqués. Pas par nostalgie, pas par flemme, et pas par désaccord avec Rust. Par contrainte. Voici laquelle.

L'enveloppe mémoire

Sur un STM32L0 qui est notre cible industrielle pour les capteurs basse consommation, nous disposons de 8 à 20 Ko de RAM et 32 à 192 Ko de flash. Le toolchain Rust génère un binaire fonctionnel, mais le landing zone est étroit : le support officiel no_std fonctionne, mais l'écosystème de crates embarquées est, à la date d'écriture, encore incomplet pour certains périphériques que nos clients spécifient.

Ce n'est pas que Rust ne tient pas. C'est que chaque semaine où l'équipe doit écrire un pilote I2C custom parce que embedded-hal n'a pas la variante du capteur qu'on a, est une semaine pendant laquelle on n'écrit pas le produit. En C, le pilote existe déjà dans le SDK du fabricant.

L'allocation prédictible

Un système embarqué industriel doit avoir une consommation mémoire déterministe sur toute sa durée de fonctionnement. Pas un pic inattendu à 95 % un mardi après-midi. Pas une fragmentation qui provoque un échec d'allocation au bout de six semaines d'uptime.

En C, on obtient cette propriété en interdisant malloc. Tout est statique, tout est dimensionné au compile-time. L'outil principal est la discipline de l'équipe, pas le langage. Rust propose une propriété similaire avec son système de types, et c'est une vraie amélioration. Mais la discipline C bien appliquée produit le même résultat, sur une base de code que toute l'équipe connaît depuis vingt ans.

PRATIQUE INTERNE Nous interdisons malloc et free dans le firmware de production. Le linker est configuré pour échouer si un appel à _sbrk apparaît dans le binaire final. Ce n'est pas Rust qui nous donne cette sécurité — c'est le linker.

La maturité du toolchain

GCC ARM a 20 ans de corrections de bugs en code généré pour Cortex-M0+. Pour un produit que nous livrons à 50 000 exemplaires et qui doit fonctionner dix ans en conditions industrielles, ça compte. Le compilateur Rust pour embarqué est excellent, mais il a moins de miles sur des cas limite : optimisations avec interrupts imbriqués, comportement sur des cores sans unité de division, interaction avec des outils de trace propriétaires.

Ces cas limites ne sont pas rares pour nous. Ils sont la norme. Un bug de compilateur qui apparaît chez un client une fois par mois en production est un problème que personne ne veut diagnostiquer.

L'interopérabilité avec les SDK fabricants

ST, NXP, Nordic, Silicon Labs publient leurs SDK en C. Leur middleware, leurs exemples, leurs drivers : en C. On peut wrapper tout ça en Rust. Mais on hérite alors du pire des deux mondes : la sécurité mémoire de Rust aux frontières, et l'unsafe de C au centre, où se passe le vrai travail.

Cette frontière unsafe est grosse, et elle n'est pas juste un détail de présentation. Elle signifie que dans nos architectures, l'avantage mémoire de Rust s'applique à une petite fraction du code et non à la majorité. Le ratio effort/bénéfice baisse en conséquence.

Là où nous n'écrivons plus de C

Cette position n'est pas idéologique. Sur les cibles avec 512 Ko de RAM et plus — typiquement Cortex-M7 ou Cortex-A embarqué — nous écrivons en Rust quand le projet le permet. La frontière est matérielle : dès que l'enveloppe mémoire permet une stack, un allocateur, et un écosystème de crates viable, Rust gagne.

Pour les outils de test, les ferme d'intégration, les pipelines de données, c'est du Rust ou du Python. Le C est confiné aux cibles où il n'a pas de substitut crédible en 2025.

Ce que nous surveillons

L'écosystème Rust embarqué progresse vite. embassy mature. Les crates HAL s'étendent. Le support des MCU Cortex-M0+ s'améliore. Notre estimation interne : dans trois à quatre ans, le calcul basculera pour la majorité de nos cibles. À ce moment-là, nous migrerons. Pas avant.

Cette phrase résume notre position sur beaucoup de décisions techniques : on migre quand le bénéfice dépasse le coût. On n'essaie pas de faire mieux que ce que la physique et l'industrie autorisent aujourd'hui. On fait ce qui marche, avec ce qui existe, et on surveille ce qui vient.

C is an old language. It's dangerous. It protects you from nothing. You can write code that compiles, passes review, and corrupts memory in production three months later. Every criticism leveled at C is accurate.

And yet, in 2025, we still use it in the majority of our embedded projects. Not out of nostalgia, not out of laziness, not out of disagreement with Rust. Out of constraint. Here it is.

The memory envelope

On a STM32L0, which is our industrial target for low-power sensors, we have 8 to 20 KB of RAM and 32 to 192 KB of flash. The Rust toolchain produces a working binary, but the landing zone is narrow: official no_std support works, but the embedded crate ecosystem is, as of writing, still incomplete for some peripherals our clients specify.

It's not that Rust doesn't fit. It's that every week the team has to write a custom I2C driver because embedded-hal doesn't cover the sensor variant we're using is a week we don't spend writing the product. In C, the driver already exists in the vendor SDK.

Predictable allocation

An industrial embedded system must have deterministic memory consumption across its entire operating lifetime. Not an unexpected spike at 95% one Tuesday afternoon. Not fragmentation that triggers an allocation failure after six weeks of uptime.

In C, we get this property by banning malloc. Everything is static, everything is sized at compile time. The main tool is team discipline, not the language. Rust offers a similar property through its type system, and that's a real improvement. But C discipline, properly applied, produces the same result on a code base the team has known for twenty years.

INTERNAL PRACTICE We forbid malloc and free in production firmware. The linker is configured to fail if a call to _sbrk appears in the final binary. Rust isn't what gives us this safety — the linker is.

Toolchain maturity

GCC ARM has 20 years of code-generation bug fixes on Cortex-M0+. For a product we ship in 50,000 units that has to work for ten years in industrial conditions, that matters. The Rust compiler for embedded is excellent, but it has fewer miles on edge cases: optimizations with nested interrupts, behavior on cores without a division unit, interaction with proprietary trace tools.

Those edge cases aren't rare for us. They're the norm. A compiler bug that shows up at a customer site once a month in production is a problem nobody wants to diagnose.

Interop with vendor SDKs

ST, NXP, Nordic, Silicon Labs ship their SDKs in C. Their middleware, their examples, their drivers: C. You can wrap it all in Rust. But then you inherit the worst of both worlds: Rust's memory safety at the boundaries, and C's unsafe in the middle, where the real work happens.

That unsafe boundary is large, and it isn't cosmetic. It means that in our architectures, Rust's memory advantage applies to a small fraction of the code rather than the majority. The effort-to-benefit ratio falls accordingly.

Where we no longer write C

This position isn't ideological. On targets with 512 KB of RAM and above — typically embedded Cortex-M7 or Cortex-A — we write Rust when the project allows. The boundary is hardware: the moment the memory envelope permits a stack, an allocator, and a viable crate ecosystem, Rust wins.

For test tooling, integration farms, data pipelines, it's Rust or Python. C is confined to targets where it has no credible substitute in 2025.

What we're watching

The embedded Rust ecosystem is progressing fast. embassy is maturing. HAL crates are expanding. Cortex-M0+ support is improving. Our internal estimate: within three to four years, the calculation will flip for most of our targets. At that point, we'll migrate. Not before.

That sentence captures our stance on many technical decisions: migrate when the benefit exceeds the cost. Don't try to outrun what physics and industry allow today. Do what works, with what exists, and watch what's coming.