Pourquoi SQLite a-t-il été écrit en C ?
(sqlite.org)- 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
- Exemple : Internal vs External BLOBs
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
Réactions sur Hacker News
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 compriseget_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_uncheckedpanicque de manière conditionnelleassert(). 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 performanceif condition { panic(err) }ne pourrait pas être utilisé comme une forme de fonction d’assertionLa 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.