Rust: Diseña con Tipos, Evita Validaciones

Fuentes: Parse, don't Validate and Type-Driven Design in Rust — ramblings of @harudagondi

Este artículo, originalmente escrito en Haskell y ahora adaptado a Rust, introduce un patrón de diseño llamado "Parse, don't Validate" y el diseño impulsado por tipos (Type-Driven Design). La idea central es evitar funciones de validación explícitas y, en su lugar, codificar las restricciones y garantías directamente en los tipos de datos. Esto lleva a un código más robusto, legible y mantenible.

El problema que aborda este patrón surge al considerar operaciones simples como la división de números. Una división por cero en Rust genera un pánico en tiempo de ejecución. Si bien se pueden agregar aserciones para mitigar esto, el problema subyacente es que los errores se detectan tarde, en tiempo de ejecución, lo que es especialmente problemático en lenguajes dinámicos. Rust, con su sistema de tipos, ofrece la posibilidad de trasladar estas validaciones al tiempo de compilación.

Una solución común es usar Option o Result para indicar posibles fallos. Sin embargo, el artículo propone una alternativa más elegante: el uso de newtypes. Un newtype es un nuevo tipo definido sobre un tipo existente (como f32), pero con restricciones adicionales codificadas en su definición. Por ejemplo, NonZeroF32 sería un f32 que garantiza que nunca sea cero. Esto se logra mediante un constructor fallible (NonZeroF32::new) que devuelve None si el valor de entrada es cero, evitando así el pánico y comunicando la restricción directamente en el tipo.

La ventaja de este enfoque es que la validación se realiza antes de la ejecución de la función, y solo una vez. En contraste, usar Option requiere validación tanto en la función como en el código que la llama, lo que puede llevar a duplicación de código y errores. Además, el diseño impulsado por tipos mejora la legibilidad y la mantenibilidad, ya que el tipo mismo comunica las restricciones, eliminando la necesidad de buscar en el código para entenderlas. Un ejemplo práctico es el tipo String en Rust, que es un newtype sobre Vec<u8> y utiliza String::from_utf8 para validar que la secuencia de bytes sea UTF-8 válida.

En resumen, "Parse, don't Validate" promueve un diseño donde las restricciones se codifican en los tipos, lo que resulta en un código más seguro, legible y fácil de mantener, al mismo tiempo que permite detectar errores en tiempo de compilación en lugar de tiempo de ejecución.