Comparación entre Ada y Rust mediante la resolución de problemas de AoC
(github.com/johnperry-math)- Se comparan las diferencias y características surgidas al resolver problemas de Advent of Code usando Ada y Rust
- Se analizan las diferencias entre el diseño de ambos lenguajes, centrados en la seguridad y la confiabilidad, y sus formas reales de escribir programas
- Las diferencias se hacen evidentes desde varios ángulos, como la biblioteca estándar de cada lenguaje, qué funcionalidades vienen integradas, el rendimiento y el estilo de manejo de errores
- Se explican casos concretos encontrados al escribir y operar código real con ejemplos prácticos sobre modularidad, genéricos, bucles, manejo de errores y más
- La experiencia de desarrollo muestra diferencias marcadas por el tipado estático, el manejo de arreglos y las interfaces de tratamiento de errores
Introducción y objetivo
- Tras usar solo Ada para resolver problemas de Advent of Code (AoC), desde 2023 el autor también empezó a escribir soluciones en Rust y Modula-2, lo que permitió compararlos directamente
- Al trasladar soluciones antes centradas en Ada hacia Rust, se hicieron palpables las diferencias estructurales y los enfoques propios de cada lenguaje
- El objetivo es dejar en claro las diferencias prácticas de uso desde la perspectiva de la seguridad del código, la confiabilidad y el diseño del lenguaje
Versiones de lenguaje usadas en la comparación
- Ada 2022 (con referencia parcial a algunas reglas de Spark 2014 cuando fue necesario)
- Rust 2021 (comparación principal basada en Rust 1.81.0)
Funcionalidades excluidas y criterios de comparación
- Las funciones más representativas de cada lenguaje (= killer features) se mencionan brevemente como comentarios dentro del texto
- Algunas capacidades no se trataron, según la experiencia personal y las necesidades reales de cada solución
- Se intenta, en la medida de lo posible, dejar de lado opiniones personales y enfocarse en las características principales
Contexto y perspectiva del autor
- Como usuario no nativo tanto de Ada como de Rust, el autor parte de experiencia con lenguajes de los años 80 como C/C++, Pascal y Modula-2
- Como resultado, el estilo de código puede diferir de los usos modernos o idiomáticos
- Puede que no siempre se trate de la implementación óptima, y según el problema a veces se eligieron soluciones intuitivas o poco convencionales
Posicionamiento de Ada y Rust
- Ada sigue siendo un lenguaje para sistemas y desarrollo embebido muy seguro y confiable, con énfasis en la legibilidad del código
- Rust destaca por la seguridad de memoria y la programación de sistemas, y durante varios años ha sido nombrado como el “lenguaje más admirado” en la encuesta de desarrolladores de Stack Overflow
- Ada, como lenguaje de alto nivel de propósito general, ofrece un espectro orientado a la lectura y el mantenimiento
- Rust apunta al desarrollo de programas de sistema de bajo nivel y ha establecido una cultura de programación segura basada en gestión explícita de memoria y tipos de error/opción
Comparación de seguridad y características estructurales
-
Ada
- Estándar ISO (especificación rigurosa)
- Facilita definir tipos adecuados a las características del problema (rangos, cantidad de dígitos, etc.)
- Permite índices de arreglos que no sean numéricos
- Existe una especificación aún más estricta llamada Spark
-
Rust
- La especificación está centrada en la documentación oficial (Reference) y el compilador
- Las declaraciones de tipos dependen de tipos de máquina (por ejemplo, f64, u32)
- El indexado de arreglos resulta natural solo con tipos numéricos
Resumen principal de la tabla de funciones/integración
- Hay diferencias en soporte para verificación de rangos en arreglos, contenedores genéricos, concurrencia, bucles etiquetados y pattern matching
- Ada usa manejo de errores basado en Exception (excepciones), mientras que Rust usa un enfoque de retorno mediante tipos Result/Option
- Rust se diferencia especialmente por su soporte de macros, pattern matching y pureza funcional
- Ada ofrece diseño por contrato y verificación en tiempo de compilación mediante DBC (Design By Contract) en Spark
- En términos de seguridad de memoria, Rust y Spark la imponen, mientras que Ada permite el uso de punteros nulos
Comparación de rendimiento y tiempo de ejecución
- Rust suele tener la reputación de ofrecer ejecución rápida pero compilación lenta, mientras que Ada tiene la reputación opuesta: compilación rápida y ejecución algo más lenta según las verificaciones activadas en tiempo de ejecución
- Según los benchmarks, Rust sufrió overflow en el problema day24 por la limitación del tipo f64, mientras que Ada pudo usar tipos de alto nivel como digits 18, permitiendo selección automática del tipo de máquina y evitando overflow con muy buen rendimiento
- Rust necesita usar un f128 no estable o bibliotecas externas, mientras que Ada puede obtener ventaja solo con declarar tipos adecuados a la especificación del compilador
Manejo de archivos y tratamiento de errores (Caso de estudio 1)
Manejo de archivos en Ada
- Usa Ada.Text_IO por defecto
- Permite abrir archivos de forma explícita, leer línea por línea y manejar líneas por rango o posición de forma relativamente intuitiva
- Cuando ocurre un error, este se maneja mediante excepciones en lugar de mensajes explícitos, y la posibilidad de error no aparece en la firma de la función
Manejo de archivos en Rust
- Usa std::fs::File y BufReader
- Al abrir un archivo, devuelve un tipo Result, dejando clara la posibilidad de error
- No ofrece acceso directo a índices de caracteres; debe procesarse necesariamente con Iterator
- Se apoya principalmente en herramientas funcionales e iterativas como map, filter, collect, sum, además de varias macros (por ejemplo, include_str!)
- Al declarar explícitamente los errores en el tipo de retorno, se logra una propagación de errores clara a nivel de función
Modularidad y genéricos (Caso de estudio 2)
Modularidad en Ada
- Se basa en packages, con una separación clara entre especificación (interfaz) e implementación
- Para reforzar la modularización, combina subpackages y sintaxis use/rename para ajustar la legibilidad
- Soporta generic en packages: permite generalizar tipos, constantes e incluso subpackages completos
Modularidad en Rust
- Organiza módulos con el sistema mod/crate, y la distinción entre especificación e implementación se automatiza mediante el generador de documentación
- Usa control de acceso pub/private con permisos declarativos
- Combina use/as para importar y renombrar
- Tiene soporte integrado para pruebas, lo que permite declarar módulos de test directamente en el código, compilarlos y ejecutarlos automáticamente
Genéricos
- Ada solo admite genéricos a nivel de package/procedure (no para tipos por sí solos)
- Rust sí permite aplicar genéricos al tipo mismo (basado en plantillas)
- Ada puede expresar con claridad propiedades adicionales como rangos de tipos mediante tipos de rango y subtipos; Rust recurre al uso de constantes de instancia
Comparación de tipos enumerados (Caso de estudio 3)
- Ada ofrece declaraciones concisas y además soporte automático para uso discreto, ordenado y en bucles/índices
- Los enum de Rust tienen una declaración similar, pero su uso con pattern matching e iteración requiere un enfoque más explícito
Conclusión
- Ada ofrece un control más estricto en tipos de especificación de alto nivel, capacidad de verificación y comprobaciones en ejecución
- Rust tiene ventaja en estilo de programación funcional, metaprogramación con macros y manejo de errores asistido por el compilador, tanto en facilidad de desarrollo como en seguridad
- En la resolución práctica de problemas, Ada muestra fortalezas en compatibilidad con código antiguo y mantenimiento, mientras que Rust destaca por su ecosistema moderno de herramientas y su soporte para seguridad y paralelismo
1 comentarios
Opiniones en Hacker News
Enlace a la explicación de Nim Subranges
Documentación relacionada
Especificación de Rust
char) como “cadenas”, así que es fácil indexarlos como simples arreglos de bytes. Rust, en cambio, fue diseñado con conciencia de Unicode desde el principio, por lo que sus cadenas son UTF-8 codificado, o sea, verdadero “texto”. Por eso Ada permite indexación aleatoria como arreglo, mientras que en Rust el concepto de cadena es distinto y siempre queda la opción de convertirla a un arreglo de bytesstr::as_bytesSitio de Prunt
Prunt GitHub
Comentario relacionado en HN
pub const SIDE_LENGTH: usize = ROW_LENGTH;Index<BirdSpecies>), y eggs[Robin] funciona pero eggs[5] da error. La diferencia es que Rust no lo soporta directamente como “arreglo” a nivel del lenguaje. Esto se vuelve especialmente valioso cuando, como en Ada, “un tipo definido por el usuario puede declararse como un entero que es subconjunto de otro”. En Rust todavía no se pueden crear enteros con rango restringido como tipos puramente definidos por el usuario (solo existen internamente cosas como NonZeroI16). Sería buenísimo que Rust llegara a soportar eso