Haskell · Rust · diseño de lenguajes
Haskell vs Rust: dos respuestas a la misma pregunta
Haskell y Rust se comparan constantemente, y la comparación suele plantearse como una competición. Ese enfoque oculta lo que de verdad resulta interesante: ambos lenguajes parten de la misma premisa, que el compilador debe impedirte escribir programas rotos, y luego toman rutas opuestas en la única decisión que da forma a todo lo demás.
En qué coinciden
Ambos cargan un peso inusual sobre el sistema de tipos. La propia descripción de Haskell es que «toda expresión en Haskell tiene un tipo que se determina en tiempo de compilación», y que su sistema de tipos fuerte «se asegura de que no haya sorpresas». El compilador de Rust impone sus reglas con la misma intención, negándose a compilar código que las viole.
En ambos casos el intercambio es el mismo: dedicas más tiempo a conseguir que el programa compile y menos a depurar una clase de fallos en tiempo de ejecución. Si ese intercambio no te gusta, ninguno de los dos lenguajes te resultará agradable, y la elección entre ellos es irrelevante.
En qué divergen: la memoria
Esta es la decisión de la que se deriva todo lo demás.
El Rust Book lo plantea como tres opciones: «Algunos lenguajes tienen garbage collection, que busca regularmente la memoria que ya no se usa mientras el programa se ejecuta; en otros lenguajes, el programador debe reservar y liberar la memoria explícitamente. Rust usa un tercer enfoque: la memoria se gestiona mediante un sistema de ownership con un conjunto de reglas que el compilador comprueba.»
La memoria se libera de forma determinista: «la memoria se devuelve automáticamente en cuanto la variable que la posee sale de su ámbito», a través de una función drop que el compilador llama en la llave de cierre. Y el coste se paga en tiempo de compilación y no en tiempo de ejecución, ya que «ninguna de las características del ownership ralentizará tu programa mientras se ejecuta».
Haskell toma la primera de esas tres opciones. GHC «viene con un garbage collector paralelo de alto rendimiento y una biblioteca de concurrencia ligera». No razonas sobre ownership porque el runtime se encarga de la recuperación por ti.
Esa única diferencia explica casi todo lo demás. Rust puede apuntar a contextos donde un garbage collector es inaceptable, y a cambio te pide satisfacer al borrow checker. Haskell elimina toda esa categoría de trabajo de tu día a día, y te pide aceptar un runtime que hace la recolección en tu lugar.
En qué divergen: la evaluación
La segunda diferencia real es cuándo ocurren las cosas.
Haskell es perezoso por defecto. Como lo expresa haskell.org, «las funciones no evalúan sus argumentos», y la consecuencia que se afirma es composicional: «esto significa que los programas se componen muy bien entre sí». Puedes definir estructuras conceptualmente infinitas y consumir solo la parte que necesitas.
Rust evalúa de forma estricta, como la mayoría de los lenguajes. Un argumento se calcula antes de la llamada.
La pereza es genuinamente potente y genuinamente más difícil de razonar en cuanto a rendimiento, porque el momento en que ocurre un cálculo ya no es donde lo escribiste. La evaluación estricta es más predecible y menos expresiva en ese aspecto concreto. Ninguna es un defecto; son valores por defecto distintos.
La pureza y lo que aporta
Haskell describe cada función como «una función en el sentido matemático (es decir, "pura")», y enumera «la transparencia referencial, la inmutabilidad y la evaluación perezosa» entre sus características. La transparencia referencial significa que una expresión puede sustituirse por su valor sin cambiar el programa, que es lo que hace seguros el razonamiento y la refactorización agresivos.
Rust no es puro, ni pretende serlo. En su lugar te da mutación controlada: el borrow checker rige quién puede mutar qué y cuándo, de modo que la mutación sigue siendo segura sin estar prohibida.
Entonces, ¿a qué pregunta estás respondiendo?
La forma útil de elegir es preguntarse qué es lo que el proyecto no puede tolerar.
Si no puede tolerar un garbage collector, porque estás escribiendo un componente de sistema operativo, un objetivo empotrado, un motor de juego o cualquier cosa con requisitos de latencia estrictos, eso lo decide. Rust existe para esa restricción.
Si no puede tolerar una lógica incorrecta, y el dominio es lo bastante complejo como para que el razonamiento en sí sea la parte difícil, la pureza y el sistema de tipos de Haskell apuntan exactamente ahí. Los compiladores, la modelización financiera y el instrumental de lenguajes se repiten en los usos reales de Haskell por ese motivo.
Si no se impone ninguna de las dos restricciones, esta comparación probablemente no sea tu factor decisivo. La disponibilidad de bibliotecas, la contratación y lo que tu equipo ya conoce importarán más que la semántica del lenguaje, y es honesto decirlo.
La versión corta
Haskell y Rust coinciden en que el compilador debe atrapar tus errores, y luego responden a dos preguntas distintas. Rust responde a «cómo consigo seguridad de memoria sin garbage collector», con ownership comprobado en tiempo de compilación y sin coste en ejecución. Haskell responde a «cómo hago que la lógica en sí sea difícil de equivocar», con pureza, pereza y un garbage collector que se ocupa de la recuperación.
No son realmente competidores. Son dos caminos desde el mismo punto de partida, y la restricción que no puedes negociar te dice en cuál estás.
Si el primero de ellos es nuevo para ti, empieza por qué es Haskell y, cuando llegues a construir proyectos, Stack frente a Cabal.
La descripción de la gestión de memoria y las citas proceden del capítulo oficial del Rust Book sobre ownership; las descripciones de la pureza, la pereza, el sistema de tipos y el garbage collector proceden de haskell.org. Ambas se comprobaron en el momento de escribir. Las implementaciones de los lenguajes evolucionan, así que verifica con la documentación actual antes de apoyarte en una afirmación concreta.