10 points par GN⁺ 2025-10-16 | 2 commentaires | Partager sur WhatsApp
  • SQLite est développé en langage C depuis ses débuts (2000) pour des raisons de performance, de compatibilité, de faibles dépendances et de stabilité
  • Le C peut être utilisé sur presque tous les OS et avec presque tous les langages, et convient particulièrement aux bibliothèques bas niveau qui doivent s’exécuter rapidement
  • Le choix du C plutôt que d’un langage orienté objet s’explique par l’extensibilité, la possibilité d’être appelé depuis divers langages, ainsi que par le manque de maturité de C++ et de Java à l’époque du développement
  • SQLite repose sur une structure en fichier unique avec très peu de dépendances, en n’utilisant qu’un minimum de fonctions de la bibliothèque standard du C
  • Des discussions existent autour d’une réécriture dans des « langages sûrs » comme Rust ou Go, mais en matière de contrôle qualité, de performance et d’appel depuis des bibliothèques, le C garde encore l’avantage

1. Pourquoi le C est le choix optimal

  • SQLite est maintenu en langage C depuis sa première version, le 29 mai 2000, jusqu’à aujourd’hui
    • Il n’existe actuellement aucun projet de réécriture dans un autre langage
  • Le C offre un contrôle proche du matériel tout en restant très portable, au point d’être parfois qualifié de « langage assembleur portable »
  • D’autres langages peuvent affirmer être « aussi rapides que le C », mais aucun ne prétend être plus rapide que le C

1.1. Performance

  • Une bibliothèque bas niveau comme SQLite étant appelée très fréquemment, elle doit fonctionner extrêmement vite
  • Le langage C est bien adapté à l’écriture de code rapide, tout en offrant une grande portabilité et un accès étroit au matériel
  • D’autres langages modernes affirment eux aussi être « aussi rapides que le C », mais dans la programmation généraliste, aucun ne peut affirmer avec certitude être plus rapide
  • Le C permet un contrôle fin de la mémoire et des ressources CPU, au point d’offrir parfois des performances 35 % supérieures à celles du système de fichiers

1.2. Compatibilité

  • Presque tous les systèmes peuvent appeler des bibliothèques écrites en C
  • Par exemple, même sur Android (basé sur Java), SQLite peut être utilisé via un adaptateur
  • Si SQLite avait été écrit en Java, il n’aurait pas pu être utilisé sur iPhone (Objective-C, Swift), ce qui aurait fortement réduit sa portée générale

1.3. Faibles dépendances

  • Le fait d’avoir été développé comme bibliothèque C implique très peu de dépendances d’exécution
  • Dans sa configuration minimale, il n’utilise que quelques fonctions très basiques de la bibliothèque standard du C (memcmp(), memcpy(), memmove(), memset(), strcmp(), strlen(), strncmp())
  • Même dans une version plus complète, il ne dépend que de quelques éléments supplémentaires comme malloc(), free() et les entrées/sorties fichiers
  • Les langages modernes exigent souvent de gros environnements d’exécution et des milliers d’interfaces

1.4. Stabilité

  • Le C est un vieux langage ennuyeux qui évolue peu, mais cela signifie aussi prévisibilité et stabilité
  • Pour construire un moteur de base de données petit, rapide et fiable comme SQLite, un langage dont les spécifications changent peu est bien adapté
  • Si les spécifications ou l’implémentation du langage changent fréquemment, cela nuit à la stabilité de SQLite

2. Pourquoi ne pas l’avoir écrit dans un langage orienté objet

  • Certains développeurs pensent qu’il est difficile d’implémenter un système complexe comme SQLite sans orienté objet, mais une bibliothèque en C++ ou en Java serait plus difficile à appeler depuis d’autres langages qu’une bibliothèque en C
  • Pour assurer la prise en charge de divers langages comme Haskell, Java et d’autres, le choix d’une bibliothèque C était pertinent
  • L’orienté objet n’est pas un langage mais un modèle de conception, et n’est donc pas limité à un langage particulier
    • Même en C, il est possible d’implémenter des modèles orientés objet avec des structures et des pointeurs de fonction
  • L’orienté objet n’est pas toujours la meilleure structure : du code procédural peut être plus clair, plus facile à maintenir et parfois plus rapide
  • Au début du développement de SQLite (vers 2000) :
    • Java était immature
    • C++ souffrait de graves problèmes de compatibilité entre compilateurs
      → À l’époque, le C était le choix le plus pratique et le plus sûr
  • Aujourd’hui encore, les bénéfices d’une réécriture de SQLite restent limités

3. Pourquoi ne pas l’avoir écrit dans un « langage sûr »

  • L’intérêt pour les langages de programmation sûrs comme Rust ou Go a augmenté récemment, mais ils n’existaient pas lorsque SQLite a été conçu à l’origine (ni durant ses dix premières années)
  • Une réécriture en Go ou en Rust pourrait introduire davantage de bugs ou entraîner une baisse de performance
  • Ces langages insèrent du code de branchement supplémentaire pour les vérifications mémoire, alors que, dans la stratégie qualité de SQLite, une couverture de branches à 100 % est essentielle, et ce point n’est pas satisfait
  • Les langages sûrs ont tendance à arrêter le programme en cas de manque de mémoire, alors que SQLite est conçu pour pouvoir se rétablir même en situation de mémoire insuffisante
  • Rust, Go et les autres restent encore des langages jeunes qui nécessitent un développement continu
  • C’est pourquoi l’équipe de SQLite soutient l’évolution des langages sûrs, tout en continuant de privilégier, pour l’implémentation de SQLite, la stabilité éprouvée du C

Malgré cela, une réécriture en Rust reste possible un jour. Une version en Go est moins probable, car Go n’aime pas assert()

  • Mais pour qu’une implémentation en Rust soit envisageable, plusieurs conditions préalables doivent être remplies :
    • Rust devra être plus mature, avec un rythme d’évolution plus lent, au point de devenir un « vieux langage ennuyeux »
    • Il faudra démontrer qu’il est possible de produire une bibliothèque généraliste pouvant être appelée depuis plusieurs langages
    • Il faudra pouvoir générer du code objet fonctionnant aussi sur des appareils sans OS, comme dans l’embarqué
    • Des outils de test de couverture de branches à 100 % devront exister pour les binaires compilés
    • Il faudra être capable de récupérer après une erreur OOM (manque de mémoire)
    • Rust devra pouvoir réaliser toutes les tâches prises en charge par le C dans SQLite sans perte de performance
  • Si un passionné de Rust (rustacean) pense que toutes ces conditions sont déjà réunies et que SQLite devrait être recodé en Rust, il est invité à contacter directement les développeurs de SQLite pour défendre son point de vue

2 commentaires

 
GN⁺ 2025-10-16
Réactions sur Hacker News
  • Même si les langages de programmation sûrs n’existaient pas lors de ses dix premières années, une réimplémentation de SQLite en Go ou en Rust risquerait probablement d’introduire plus de bugs qu’elle n’en corrigerait, et pourrait aussi être plus lente. Si un code pratiquement sans bugs a déjà été obtenu au prix d’un temps énorme et de tests massifs, alors dans un contexte où le taux de changement est faible, le langage importe peu. À ce niveau-là, même de l’assembleur conviendrait
    • Le point selon lequel « un faible taux de changement entraîne moins de problèmes » a aussi été expliqué sur le Google Security Blog. Il y est avancé que la plupart des problèmes de sûreté mémoire apparaissent dans le nouveau code, et que le code devient plus sûr avec le temps lien connexe
    • Côté Rust, des projets comme Turso avancent de façon assez active Turso
    • Certains soutiennent aussi qu’il ne faut pas réécrire en Rust les utilitaires de base de Linux. Ils estiment qu’il n’y a pas de raison de réécrire des logiciels utilisés depuis des décennies et dont la plupart des bugs ont déjà été éliminés
    • Zig semble bien adapté pour remplacer une partie du code C. Il s’intègre bien avec Python et avec les binaires C existants. La philosophie de Go est appréciable, mais il avait la limite d’être difficile à optimiser et d’exiger des développeurs très compétents. Rust pourrait aussi convenir, mais il a été bien plus simple de continuer à utiliser le C existant tout en introduisant Zig progressivement. Même si nous n’arrivons pas à éliminer totalement les bugs du code C, passer à Rust paraît difficile en pratique
    • Il existe déjà une implémentation de sqlite portée en Go cznic/sqlite
  • Au-delà du fait que le C était le meilleur choix au moment du développement initial de SQLite et de ses avantages actuels, il n’y a pas de raison particulière de réécrire SQLite dans un autre langage. Tout le monde peut implémenter une base de données SQL légère, donc il est possible de créer une nouvelle implémentation en Rust, C++, Go, Lisp ou n’importe quel autre langage. Il n’y a pas lieu d’abandonner une implémentation existante en C qui fonctionne bien, ni de forcer les développeurs qui maintiennent SQLite en C depuis plus de 25 ans à apprendre un nouveau langage et à tout refaire depuis zéro
    • Dans de nombreuses communautés de langage, il existe une tendance à imposer aux autres ce que l’on préfère, et l’adoption des langages a en partie dérivé vers une sorte de lutte à somme nulle. Lorsqu’un projet est développé dans un certain langage, le simple fait de ne pas utiliser celui-là semble remettre en cause la nécessité des autres. En réalité, les options sont bien plus nombreuses, et même en cas de réécriture, la liste comprendrait Rust, Go, D, Lisp, Julia et d’autres
    • En pratique, les développeurs de SQLite sont ouverts à une réécriture en Rust. Si Rust remplit les conditions préalables nécessaires, une refonte reste possible. Il est même suggéré aux enthousiastes de Rust de contacter directement les développeurs de SQLite
    • Il existe déjà des projets comme rqlite et turso implémentés en Rust
    • Il existe un adaptateur écrit en Go qui permet d’utiliser sqlite en golang sans cgo. Désormais, sqlite n’est plus seulement une bibliothèque C en soi, c’est aussi un format de fichier de base de données. On peut imaginer qu’une implémentation pure Rust apparaisse à l’avenir et devienne un jour l’implémentation principale
    • La tendance actuelle à considérer comme obsolète toute technologie vieille de plus de cinq ans est jugée frustrante. Il faudrait davantage de respect pour les technologies polies pendant de longues années
  • Les langages sûrs génèrent des branches supplémentaires pour vérifier les limites lors des accès aux tableaux, mais dans un code correct ces branches ne s’exécutent pas réellement. Autrement dit, il devient difficile d’obtenir 100 % de tests de branches, et ce point est lié à la stratégie qualité de SQLite. Ce raisonnement nouveau paraît intéressant
    • Si l’on est certain qu’une branche du code ne sera jamais exécutée, n’est-il pas inutile de la tester ? Cela donne l’impression de sacrifier la sûreté pour atteindre 100 % de couverture de test
    • Dans les langages sûrs, le compilateur ajoute automatiquement du code défensif du type if (i >= array_length) panic("index out of bounds"), et ce code lui-même a déjà été bien testé par le compilateur Rust, donc il n’y aurait pas de raison de s’en inquiéter. La question est de savoir si cette logique est bien comprise
    • Pour un expert comme Dr Hipp et un projet comme sqlite, cet argument peut aussi se défendre
    • Avec des méthodes comme get_unchecked() en Rust, il est aussi possible d’accéder sans bounds check, ce qui permet d’améliorer les performances tout en restant sûr documentation de get_unchecked
    • On se demande s’il ne serait pas possible d’atténuer ce problème en exemptant de l’obligation de couverture les branches qui ne mènent à panic que de manière conditionnelle
  • SQLite laisse ouverte la possibilité d’être réécrit un jour en Rust, tandis que Go semble peu probable à cause des contraintes liées à assert(). Pour une migration vers Rust, les conditions préalables seraient les suivantes : Rust devrait évoluer beaucoup moins pendant une plus longue période, convenir à l’écriture de bibliothèques généralistes, fonctionner aussi sur de l’embarqué sans OS, disposer d’outils de couverture de branches à 100 %, offrir un mécanisme de gestion des erreurs OOM, et remplacer le rôle du C sans baisse de performance
    • Depuis Rust 1.0, Rust évolue de manière compatible depuis plus de dix ans. Il y a une différence entre ceux qui veulent qu’il cesse totalement d’évoluer, et ceux qui acceptent qu’il continue. Le développement de bibliothèques généralistes est déjà démontré, et le support de l’embarqué sans OS est clairement possible. Pour la couverture de branches, l’auteur dit ne pas être spécialiste, mais des travaux sont en cours, notamment chez Ferrocene. Le langage Rust lui-même n’alloue pas de mémoire, donc le traitement de l’OOM peut se décider au niveau de la bibliothèque standard. La question des performances peut varier selon la manière dont on la définit
    • On se demande aussi si, en Go, if condition { panic(err) } ne pourrait pas être utilisé comme une forme de fonction d’assertion
  • La plupart des arguments semblent plausibles au premier abord, mais paraissent moins parfaits quand on les examine en détail. Il suffirait peut-être d’expliquer clairement pourquoi le C a été choisi vers 2000, puis d’accepter aujourd’hui une base de code bien polie. Les arguments annexes semblent parfois réfutables
    • On aimerait savoir précisément quels arguments seraient réfutables
    • Les arguments présentés peuvent servir à maintenir une base de code héritée, mais pour convaincre de nouveaux développeurs d’adopter le C plutôt qu’un langage plus complexe, il faudrait davantage de justifications
    • (Ce document a été rédigé en 2017)
    • Il est supposé qu’un document long et détaillé a été écrit pour répondre à de nombreuses questions répétées du type « pourquoi ne pas le réécrire en X ? »
  • Des projets qui portent SQLite vers Go de manière automatisée existent déjà depuis plusieurs années et sont activement diffusés modernc.org/sqlite. Ils passent bien la même suite de tests. En revanche, la version Go est nettement plus lente, même si, dans bien des cas, la commodité d’un portage natif Go compte plus que la vitesse elle-même. En conclusion, il paraît plus réaliste de traduire automatiquement SQLite depuis le C que de le réécrire en Go, Rust, Zig, Nim, Swift ou d’autres langages
    • La suite de tests publique est réussie, mais il existe apparemment aussi chez SQLite une suite de tests interne bien plus sévère
    • Réussir la suite de tests ne signifie pas qu’il n’y a aucun bug ; de nouveaux cas limites ou des problèmes de performances peuvent subsister
  • La documentation officielle explique bien « pourquoi SQLite a été développé en C », mais quand on entend « pourquoi pas Rust ? », la première réaction est plutôt de demander « pourquoi faudrait-il absolument que ce soit Rust ? »
    • Certains pensent que cela vient du titre qui oriente la question
    • Il existe déjà de tels projets de réécriture en Rust : tursodatabase/turso ainsi qu’un billet de blog qui discute aussi du Why
    • Le ton suggère qu’on pourrait tout aussi bien demander pourquoi SQLite a été écrit en C plutôt qu’en BASIC
  • Plus on lit sur le code, l’usage des logiciels et les réécritures, plus un problème ressort : lorsqu’une réécriture vise seulement l’« équivalence fonctionnelle », elle risque facilement d’omettre la multitude d’exceptions et de correctifs accumulés au fil du temps. Le logiciel finit alors par se casser à nouveau, ou par dégrader des comportements qui marchaient auparavant. Ce type de réécriture exige beaucoup d’insistance et de prudence, et une restauration à 100 % paraît difficile. Même des bibliothèques importantes comme SDL n’y échappent pas. On peut s’attendre à des versions qui cassent régulièrement et à des plaintes d’utilisateurs. Le C survivra probablement longtemps, même après que Rust sera devenu dominant. La réécriture ne devrait pas être le choix par défaut
  • Il est plus intéressant encore que DuckDB ait été écrit en C++ plutôt qu’en Rust : DuckDB est un projet récent, apparu en 2019, qui aurait pu adopter Rust, mais a finalement choisi C++. DuckDB est nouveau, et sa base de code est bien plus petite que celle de SQLite
    • Il semble que l’équipe de DuckDB maîtrisait bien le C++ et faisait confiance à l’auto-vectorisation du compilateur. À l’époque (2019), Rust n’offrait pas encore de support SIMD de haut niveau clairement établi. Ils ne voulaient pas maintenir du code SIMD écrit à la main
    • Si l’objectif est la performance maximale, C++ peut produire des binaires plus rapides avec moins de code. Le C++ moderne offre aussi davantage de sûreté à la compilation, ce qui le rend adapté à du code comme celui d’une base de données
    • En C++ moderne, cela paraît tout à fait acceptable
  • Il y a déjà eu de nombreux débats sur les réécritures de SQLite en 2021, en 2018
    • Le commentaire de tptacek est intéressant : dans un document précédent, il y avait une section sur la sécurité, mais elle a disparu dans la version récente. Le C constitue bien une vulnérabilité de sécurité claire, y compris pour SQLite. Dans l’ancienne version, il était expliqué que « SQLite n’est pas une bibliothèque particulièrement sensible du point de vue de la sécurité ». L’exécution de SQL non fiable était déjà présentée comme un problème plus important, et, pour l’import de fichiers externes, des protections ainsi que des tests rigoureux devaient empêcher les problèmes, avec en plus des routines de validation préalable document archivé de 2021
 
aer0700 2025-10-16

La mention selon laquelle le C constitue aussi un risque de sécurité pour SQLite vaut-elle même si l’on écrit des tests de façon suffisamment rigoureuse et que l’on est un développeur suffisamment expérimenté ? Le problème peut venir de la logique ou du processus de développement, mais j’ai du mal à comprendre en quoi le langage lui-même constituerait une vulnérabilité de sécurité. En réalité, il n’existe presque aucun programme qui ne dépende pas d’une infrastructure écrite en C.