10 puntos por GN⁺ 2025-10-16 | 2 comentarios | Compartir por WhatsApp
  • SQLite se ha desarrollado en lenguaje C desde sus inicios (año 2000) por razones de rendimiento, compatibilidad, pocas dependencias y estabilidad
  • C puede usarse en casi todos los sistemas operativos y lenguajes, y en particular permite un funcionamiento rápido como biblioteca de bajo nivel
  • La razón para elegir C en lugar de un lenguaje orientado a objetos fue la extensibilidad, la posibilidad de invocarlo desde distintos lenguajes y la inmadurez de C++ y Java en esa época
  • SQLite tiene una estructura de un solo archivo con casi ninguna dependencia, y usa solo las funciones mínimas de la biblioteca estándar de C
  • Aunque se ha discutido reescribirlo en "lenguajes seguros" como Rust y Go, C sigue teniendo ventaja en control de calidad, rendimiento y capacidad de ser llamado desde bibliotecas

1. Por qué C es la mejor opción

  • SQLite se ha mantenido en lenguaje C desde su primer desarrollo el 29 de mayo de 2000 hasta hoy
    • Por ahora no hay planes de reescribirlo en otro lenguaje
  • C ofrece control cercano al hardware y al mismo tiempo una gran portabilidad, por lo que se le llama un “lenguaje ensamblador portátil”
  • Otros lenguajes pueden afirmar que son “tan rápidos como C”, pero ninguno afirma ser más rápido que C

1.1. Rendimiento

  • Una biblioteca de bajo nivel como SQLite se invoca con mucha frecuencia, así que debe funcionar muy rápido
  • El lenguaje C es adecuado para escribir código veloz, con alta portabilidad y acceso estrecho al hardware
  • Otros lenguajes modernos también dicen ser “tan rápidos como C”, pero en programación de propósito general no hay ninguno que con certeza pueda decir que es más rápido que C
  • C permite controlar con detalle la memoria y los recursos de CPU, por lo que incluso puede mostrar un rendimiento 35% superior al del sistema de archivos

1.2. Compatibilidad

  • Casi todos los sistemas pueden invocar bibliotecas escritas en C
  • Por ejemplo, incluso en Android (basado en Java) se puede usar SQLite mediante un adaptador
  • Si SQLite hubiera sido escrito en Java, no podría usarse en iPhone (Objective-C, Swift), lo que reduciría mucho su propósito general

1.3. Bajas dependencias

  • Al estar desarrollado como biblioteca en C, tiene muy pocas dependencias en tiempo de ejecución
  • En la configuración mínima solo usa funciones muy básicas de la biblioteca estándar de C (memcmp(), memcpy(), memmove(), memset(), strcmp(), strlen(), strncmp())
  • Incluso en una compilación más completa solo tiene unas pocas dependencias, como malloc(), free() y entrada/salida de archivos
  • Los lenguajes modernos a menudo requieren múltiples runtimes grandes y miles de interfaces

1.4. Estabilidad

  • C es un lenguaje viejo, aburrido y con pocos cambios, pero eso significa previsibilidad y estabilidad
  • Para crear un motor de base de datos pequeño, rápido y confiable como SQLite, conviene un lenguaje cuya especificación no cambie con frecuencia
  • Si la especificación o la implementación del lenguaje cambian continuamente, eso perjudica la estabilidad de SQLite

2. Por qué no fue escrito en un lenguaje orientado a objetos

  • Algunos desarrolladores creen que sería difícil implementar un sistema complejo como SQLite sin orientación a objetos, pero frente a C, crear una biblioteca en C++ o Java hace más difícil llamarla desde otros lenguajes
  • Para dar soporte a distintos lenguajes como Haskell o Java, la elección de una biblioteca en C era razonable
  • La orientación a objetos no es un lenguaje, sino un patrón de diseño, así que no está limitada a un lenguaje en particular
    • Incluso en C es posible implementar patrones orientados a objetos con structs y punteros a función
  • La orientación a objetos no siempre es la mejor estructura; a veces el código procedural es más claro, más fácil de mantener y da resultados más rápidos
  • En los inicios del desarrollo de SQLite (alrededor del año 2000):
    • Java era inmaduro
    • C++ tenía graves problemas de compatibilidad entre compiladores
      → En ese momento, C era la opción más práctica y segura
  • Incluso hoy, los beneficios de reescribir SQLite siguen siendo escasos

3. Por qué no fue escrito en un "lenguaje seguro"

  • Últimamente ha crecido el interés por lenguajes de programación seguros como Rust y Go, pero cuando SQLite se desarrolló originalmente (durante sus primeros 10 años), esos lenguajes no existían
  • Si se reescribiera en Go o Rust, podrían aparecer más bugs o disminuir el rendimiento
  • Estos lenguajes insertan código de bifurcación adicional para comprobaciones de memoria y similares, y en la estrategia de calidad de SQLite es importante la cobertura de ramas del 100%, algo que aquí no se cumple
  • Los lenguajes seguros suelen terminar el programa en situaciones de falta de memoria, pero SQLite está diseñado para poder recuperarse incluso en escenarios de memoria insuficiente
  • Rust, Go y otros siguen siendo lenguajes jóvenes y todavía necesitan desarrollo continuo
  • Por eso, aunque el equipo de SQLite apoya el avance de los lenguajes seguros, en la implementación de SQLite sigue priorizando la estabilidad comprobada de C

Aun así, existe la posibilidad de que algún día se reescriba en Rust. Es poco probable que se escriba en Go, porque a Go no le gusta assert()

  • Pero para que pueda escribirse en Rust, hay condiciones previas:
    • Rust tendría que madurar más y ralentizar su ciclo de cambios hasta convertirse en un “lenguaje viejo y aburrido”
    • Debe demostrarse que puede crear una biblioteca de propósito general invocable desde múltiples lenguajes
    • Debe poder producir código objeto que funcione incluso en dispositivos sin sistema operativo, como sistemas embebidos
    • Deben existir herramientas de prueba de cobertura de ramas del 100% sobre los binarios compilados
    • Debe ser posible recuperarse de errores OOM (falta de memoria)
    • Rust debe poder realizar todo lo que hace C en SQLite sin degradación de rendimiento
  • Si algún entusiasta de Rust (rustacean) cree que estas condiciones ya se cumplen y que SQLite debería volver a codificarse en Rust, se le recomienda contactar directamente a los desarrolladores de SQLite para defender su postura

2 comentarios

 
GN⁺ 2025-10-16
Comentarios en Hacker News
  • Aunque los lenguajes de programación seguros no existían durante los primeros 10 años, si hoy se reimplementara SQLite en Go o Rust, probablemente se introducirían más bugs de los que se corregirían, y además podría volverse más lento. Si ya se invirtió una enorme cantidad de tiempo y pruebas para completar un código sin errores, en un contexto de baja tasa de cambios da igual en qué lenguaje esté escrito. Incluso podría estar en ensamblador y seguiría dando lo mismo
    • En el Google Security Blog explican esa idea de que “si la tasa de cambios es baja, hay menos problemas”. Allí sostienen que la mayoría de los problemas de seguridad de memoria surgen en código nuevo, y que el código se vuelve más seguro con el tiempo enlace relacionado
    • Del lado de Rust, proyectos como Turso se están moviendo bastante Turso
    • También hay quienes dicen que no se deberían reescribir en Rust las utilidades base de Linux. Consideran que no vale la pena rehacer software que ya lleva décadas usándose y del que ya se eliminaron la mayoría de los bugs
    • Creo que Zig es bueno para reemplazar parte del código en C. Se lleva bien con Python y con binarios C existentes. La filosofía de Go me gusta, pero tenía la limitación de que es difícil optimizar y hacen falta desarrolladores muy buenos. Rust también podría usarse, pero fue mucho más fácil seguir usando C existente e introducir Zig gradualmente. No hemos podido eliminar por completo los bugs del código C, pero sentimos que migrar a Rust no es realista
    • Ya existe una implementación de sqlite porteada a Go cznic/sqlite
  • Además de que C fue la mejor elección cuando SQLite se desarrolló por primera vez y de las ventajas que sigue teniendo hoy, no veo una razón especial para reescribir SQLite en otro lenguaje. Cualquiera puede implementar una base de datos SQL liviana, así que se puede hacer una implementación nueva en Rust, C++, Go, Lisp o el lenguaje que se quiera. No tiene sentido tirar una implementación existente en C que funciona bien, ni forzar a los desarrolladores que han mantenido SQLite en C por más de 25 años a aprender un lenguaje nuevo y rehacerlo desde cero
    • Siento que en muchos fandoms de lenguajes hay una tendencia a imponer a otros lo que uno quiere, y que la adopción de lenguajes se ha convertido en una especie de pelea de suma cero. Si un proyecto está desarrollado en cierto lenguaje, el simple hecho de no usar otro ya hace que algunos cuestionen la necesidad de ese otro lenguaje. En realidad las opciones son mucho más diversas, y aunque se reescribiera en otro lenguaje, el menú incluiría Rust, Go, D, Lisp, Julia y muchos más
    • En la práctica, los desarrolladores de SQLite están abiertos a una reescritura en Rust. Incluso dicen que podría hacerse si Rust cumple ciertas condiciones previas. También indican que, si alguien es fan de Rust, puede comunicarse directamente con los desarrolladores de SQLite
    • Ya existen proyectos implementados en Rust, como rqlite y turso
    • Hay adaptadores escritos en Go que permiten usar sqlite en golang sin cgo. A estas alturas sqlite ya no es solo una biblioteca en C, sino también un formato de archivo de base de datos. Creo que en el futuro podría aparecer una implementación pure rust, y quizá algún día llegue a ser la implementación principal
    • Me frustra esa moda actual de despreciar tecnologías de más de 5 años como si fueran obsoletas. Creo que hace falta más respeto por tecnologías refinadas durante mucho tiempo
  • Los lenguajes seguros generan ramas adicionales para verificar límites en accesos a arreglos, pero en código correcto esas ramas en realidad nunca se ejecutan. Es decir, es difícil lograr 100% de pruebas de ramas, y eso se relaciona con la estrategia de calidad de SQLite. Me resultó interesante escuchar esta lógica nueva
    • Si uno puede estar seguro de que esa rama del código nunca se ejecutará, ¿entonces realmente hace falta probarla? Da la impresión de que se sacrifica seguridad solo para alcanzar 100% de cobertura de pruebas
    • En lenguajes seguros, el compilador agrega automáticamente código defensivo como if (i >= array_length) panic("index out of bounds"), pero ese código en sí ya fue bien probado por el compilador de Rust, así que no debería preocuparnos. Me pregunto si estoy entendiendo bien esa lógica
    • En el caso de un experto como Dr. Hipp y de un proyecto como sqlite, creo que ese argumento también tiene cierto peso
    • En Rust también se puede acceder sin bounds check usando cosas como get_unchecked(), lo que permitiría mantener seguridad y mejorar el rendimiento documentación de get_unchecked
    • Me pregunto si este problema no podría reducirse haciendo que las ramas que condicionalmente terminan en panic no sean obligatorias para cobertura
  • SQLite deja abierta la posibilidad de reescribirse algún día en Rust, mientras que en Go parece poco probable por las limitaciones relacionadas con assert(). Consideran que para migrar a Rust se necesitan condiciones previas como estas: que Rust pase más tiempo con pocos cambios, que sea apto para escribir bibliotecas de propósito general, que funcione también en embebidos sin sistema operativo, que exista tooling para 100% de cobertura de ramas, que haya un mecanismo para manejar errores OOM, y que pueda reemplazar el rol de C sin pérdida de rendimiento
    • Rust ha evolucionado manteniendo compatibilidad por más de 10 años desde Rust 1.0. Hay una diferencia entre quienes quieren que deje de cambiar por completo y quienes aceptan que siga cambiando. El desarrollo de bibliotecas generales ya está probado, y el soporte para embebidos sin sistema operativo claramente es posible. Sobre cobertura de ramas no sé mucho porque no soy especialista, pero se está trabajando en eso en lugares como Ferrocene. El lenguaje Rust en sí no asigna memoria, así que el manejo de OOM puede decidirse a nivel de la biblioteca estándar. El tema del rendimiento puede interpretarse distinto según cómo se lo defina
    • Me pregunto si en Go no se podría usar if condition { panic(err) } como una especie de función assert
  • La mayoría de los argumentos al principio suenan plausibles, pero cuando se los revisa con cuidado no son perfectos. Pienso que bastaría con explicar claramente por qué se eligió C alrededor del año 2000, y que hoy simplemente habría que aceptar que existe una base de código bien pulida. Los argumentos adicionales sí parecen discutibles
    • Me gustaría escuchar específicamente qué argumentos son refutables
    • Los argumentos presentados pueden servir para mantener una base de código heredada, pero si se quiere convencer a desarrolladores nuevos de adoptar C en vez de un lenguaje más complejo, hace falta una justificación más sólida
    • (Ese documento fue escrito en 2017)
    • Supongo que escribieron un documento largo y detallado para responder a las muchas preguntas de “¿por qué no reescribirlo en X?” acumuladas durante años
  • Ya desde hace años existe un proyecto que traslada SQLite a Go de forma automatizada y se distribuye activamente modernc.org/sqlite. También pasa bien el mismo test suite. Aun así, la versión en Go es bastante más lenta, y muchas veces importa más la comodidad de una portabilidad nativa a Go que la velocidad en sí. En conclusión, me parece que, más que reescribir SQLite en Go, Rust, Zig, Nim o Swift, lo realista es traducirlo automáticamente desde C
    • He oído que pasa el test suite público, pero que SQLite también tiene un test suite interno mucho más exigente
    • Pasar el test suite no significa que no haya bugs; todavía pueden quedar casos límite nuevos o problemas de rendimiento
  • “¿Por qué SQLite fue desarrollado en C?” está bien explicado en la documentación oficial, pero cuando oigo la pregunta “¿por qué no Rust?”, lo primero que se me ocurre más bien es “¿por qué tendría que ser Rust?”
    • Hay quien opina que eso se debe al título que se le puso encima
    • Ya existen proyectos de este tipo de reescritura en Rust: tursodatabase/turso y también hubo una discusión sobre el Why en esta entrada de blog
    • El tono es que también se podría preguntar por qué SQLite fue escrito en C y no en BASIC
  • Cuanto más leo sobre programación, uso de software y reescrituras, más veo un problema: cuando una reescritura se hace con el único objetivo de alcanzar “equivalencia funcional”, es fácil que se pierdan muchísimos manejos de excepciones y parches acumulados con los años. Al final el software vuelve a romperse, o cosas que antes funcionaban bien dejan de hacerlo. Este tipo de reescrituras requiere mucho énfasis y mucha cautela, y creo que una restauración al 100% es difícil. Pasa lo mismo con bibliotecas importantes como SDL. Es de esperar una sucesión de releases rotos y quejas de usuarios. Creo que C va a seguir vivo mucho tiempo, incluso después de que Rust se vuelva dominante. Las reescrituras no deberían ser la opción por defecto
  • Me parece más interesante que DuckDB esté escrito en C++ y no en Rust. DuckDB apareció en 2019 como un proyecto nuevo, así que podría haberse esperado que adoptara Rust, pero al final eligió C++. DuckDB es nuevo, y su base de código también es mucho más pequeña que la de SQLite
    • Escuché que el equipo de DuckDB eligió C++ porque se sentía cómodo con ese lenguaje y confiaba en la autovectorización del compilador. En ese momento (2019), Rust no tenía un soporte SIMD de alto nivel claramente definido. No querían mantener código SIMD hecho a mano
    • Si el objetivo es el máximo rendimiento, creo que C++ puede producir binarios más rápidos con menos código. El C++ moderno también tiene bastante seguridad en tiempo de compilación, así que encaja bien con código como el de una base de datos
    • Creo que si se escribe en C++ moderno, está bien
  • Antes ya hubo muchas discusiones sobre reescribir SQLite en 2021, en 2018
    • El comentario de tptacek es interesante: en documentos anteriores había un párrafo sobre seguridad, pero desapareció en la versión más reciente. C sí representa una vulnerabilidad de seguridad clara también para SQLite. En una versión anterior se explicaba que “SQLite no es una biblioteca particularmente sensible en términos de seguridad”. Se decía que ejecutar SQL no confiable ya era de por sí un problema mayor, y que para la importación de archivos externos se evitaban problemas mediante código defensivo, pruebas rigurosas y rutinas de validación previas documento archivado de 2021
 
aer0700 2025-10-16

La frase de que C también representa un riesgo de seguridad para SQLite, ¿aplica incluso si se escriben pruebas suficientemente buenas y si los desarrolladores son lo bastante experimentados? Puede que el problema esté en la lógica o en el proceso de desarrollo, pero me cuesta entender que el lenguaje en sí sea una vulnerabilidad de seguridad. De hecho, casi no hay programas que no dependan de infraestructura escrita en C.