Notxor tiene un blog

Defenestrando la vida

Rust por encima, comparando con erlang

2021-01-06

Durante el 2020 he dedicado tiempo a aprender erlang. Ya he dicho muchas veces que me gustan los lenguajes veteranos y supongo que alguien que lea sólo el título estará pensando que voy a traicionar esos gustos. Sin embargo, estos días encontré en algún sitio un enlace a un artículo que comparaba las características de erlang con las de Rust, lo que me lleva a evaluarlo como próxima adquisición. En este artículo hablaré de lo que he ido cotilleando sobre este lenguaje y por qué me ha interesado. También he leído que es un poco puñetero y que lanza errores constantemente que no te permiten compilar y tienes que retocar el código, pero entiendo que eso forma parte de la seguridad que maneja a la hora de crear los ejecutables, por tanto ¿eso es bueno o es malo?

Después de trabajar un poco y hacer algún que otro proyecto con erlang, le he encontrado algunas características que me han encantado y que me gustaría tener en cualquier otro lenguaje. El caso es que mirando cosas sobre erlang llegué a un artículo que lo comparaba con Rust. La tentación de probar el lenguaje fue grande, porque algunas características de las que me encantan de erlang están presentes en dicho lenguaje. Además cuando me puse a investigar un poco más, todos alaban la seguridad y la velocidad de los ejecutables que genera.

Características

Quizá sea más fácil si enumero las características de erlang que me gustan y vamos a ver cuáles proporciona Rust. Mirando en la documentación encontré una lista de lenguajes que han influido a éste. El caso es que aparecen en ella algunos lenguajes que me resultan gratos:

  • erlang: de éste toman el paso de mensajes y ha influido en otras características.
  • scheme: de él toma los macros.
  • ruby: de aquí viene la sintaxis de closures.

Hay varios lenguajes más que tienen características interesantes: Haskell, OCaml, C++, etc. Pero vamos a ver las cosas que me gustaría encontrar y que erlang me proporciona.

Inmutabilidad

En erlang la asignación de las variables no puede cambiar. De primeras puede parecer complicado de entender que las variables no varíen, pero a la larga es una bendición el que no lo hagan. Cada expresión es siempre idéntica a cuando ha sido definida y se eliminan todos los errores que suceden en otros lenguajes cuando una variable cambia a un valor no esperado.

El caso es que Rust tiene variables que pueden mutar, pero por defecto son inmutables. También supongo que muchos errores de compilación de los que se quejan algunos usuarios pueden venir de este lado: intentar asignar un nuevo valor a una variable inmutable. Por lo tanto, si al final se decide que sea mutable tendrás que pensar por qué, qué ventajas tiene poder modificarla y si el proceso de hacerlo es suficientemente seguro.

Concordancia de patrones

Esta es una de las características que más me han gustado de erlang. Comprobar no sólo los valores, sino la estructura o el tipo de las variables y reaccionar a esos patrones con el código correspondiente es una forma muy potente, sencilla y rápida de escribir código que se comporta de modo polimórfico. Porque además, esa comprobación se puede llevar a cabo en cualquier parte del código: en las llamadas, dentro de las funciones, es igual. Permite una flexibilidad enorme a la hora de escribir código.

Por lo que he visto Rust no llega al mismo nivel y también tiene la posibilidad de utilizar la concordancia de patrones en estructuras de código parecidas a las estructuras case de erlang. Cuando avance un poco más en el uso de este lenguaje veré si es lo que espero. De todas formas, cada lenguaje tiene sus caminos.

Tuples y listas

La manera en la que erlang utiliza las estructuras de datos, no sólo los tuples, pero pincipalmente éstos, es una forma muy inteligente de organizar la información con la que trabaja el programa. Una vez que defines un tuple o una lista, se mantienen inmutables. Pero puedes organizar los datos de cualquier manera que te resulte cómodo y luego descomponerlo con patrones de concordancia de manera rápida, con una asignación.

Por lo que he visto en Rust también hay tuples y equivalentes a las listas tiene vectores y arrays. Uno de esos tipos es inmutable (arrays), más recomendable cuando se conoce el tamaño en tiempo de compilación. Mientras que los vectores son conjuntos de datos que pueden ajustarse en tiempo de ejecución. Veremos cómo se comportan estos tipos de datos.

Registros y ETS1

Los registros son estructuras que pueden almacenar cualquier tipo de dato válido de erlang. Son estructuras fijas que se definen junto al programa y no pueden cambiarse durante la ejecución. No se pueden añadir o quitar campos en medio de la ejecución. Además esos registros se pueden organizar en tablas ETS, ordenados por un campo que funciona como índice de la tabla. Las tablas se pueden compartir entre varios procesos, por lo que funciona como una forma de compartir datos entre los distintos componentes del sistema. Si no quieres que la tabla sea compartida se puede definir un proceso propietario que sea el único encargado de acceder a ella, como lectura/escritura o como escritura, de esta forma se puede garantizar la integridad de los datos. Todo esto, sin mencionar Mnesia que es una base de datos que puedo organizar esas tablas ETS y almacenar datos relacionados en ellas. No es SQL pero se pueden organizar consultas equivalentes.

Parece ser que Rust proporciona structs. Sin embargo, no parece que haya algo similar a ETS. Aunque supongo que un vector de structs puede servirnos también, no es lo mismo que tener tablas con sus índices para encontrar los datos. Espero que la velocidad del lenguaje compense la falta de estructura en este sentido y sea fácil conseguir algo parecido.

Iteración

Como muchos otros lenguajes funcionales, erlang proporciona el mecanismo de iteración como su principal modo para realizar bucles. Además, en la librería lists proporciona algunas funciones iterativas como map, foreach, filter, etc. Además, la potencia de la iteración sobre la estructura cabeza-cola, [H|T], como en otros lenguajes funcionales es algo que se emplea con profusión. Eso sin contar los maravillosos bloques de código, que incluso Python copió para realizar operaciones rápidas, tipo:

%% Devuelve una lista con el doble de los términos impares
[X * 2 || X <- List, (X rem 2) /= 0].

Parece ser que Rust cuenta con las instrucciones de bucle habituales de otros lenguajes como while, for, etc. Parece que está menos enfocado a la iteración, aunque parece que también se puede utilizar, no es su forma óptima. Entiendo, que en este caso será mejor utilizar esas estructuras de bucle en lugar la iteración.

Comunicación entre procesos

Al principio, la forma de comunicarse que tienen los procesos de erlang me pareció liosa. En cuanto comprendes que funciona de manera asíncrona, como enviar un mensaje depositándolo en un buzón y esperar a que te contesten ─o no─, te das cuenta que es lo más simple que hay. Al final, montar un componente es tan sencillo como levantar un proceso, abrir un buzón para él e intercambiar mensajes con los demás procesos.

La concurrencia es una de esas cosas que le dan caché a erlang y es la principal característica que mencionan todos para decantarse en el uso de este lenguaje. Los procesos concurrentes de otros lenguajes, compartiendo memoria, estableciendo semáforos o banderas, bloqueos, etc. es algo mucho más lioso que el mecanismo de erlang.

Por lo que he leído hasta ahora sobre Rust, también está enfocado a la concurrencia. Aún no sé qué mecanismos involucra.

Documentación

La documentación con la que viene erlang es completa y suficiente para consultas rápidas de las distintas librerías con las que puedes trabajar. Además son accesibles desde el propio Emacs para realizar esas consultas.

Por lo que he visto, Rust es bastante agradecido en este aspecto también y viene con una documentación bastante completa. Además en su página web puedes encontrar enlaces donde poder aprender y consultar los distintos aspectos del lenguaje. Además, después de mirar un poco por encima, la ayuda viene en ficheros html y para leerla cómodamente se lanza el navegador.

Rust

El caso es que me he puesto a investigar algo más sobre Rust y a parte de todo lo que se puede encontrar en la wikipedia, donde te explica que es un lenguaje desarrollado por la Fundación Mozilla para el desarrollo de bajo nivel, o de sistemas, superando las dificultades que presenta C, he visto en su página algo más de información.

La curiosidad, antes de meterme en faena, sin tener muy claro por qué merece la pena aprenderlo, me llevó a investigar si a priori el esfuerzo compensaría. Por ejemplo, siendo un lenguaje compilado me pregunté cómo sería su desempeño. Sin tener conocimientos aún suficientes para hacer comparaciones entre lenguajes busqué datos para compararlo en Benchmark Game. Concretamente:

Compararlo con otros lenguajes como Java, Python o erlang me parece injusto, porque no son lenguajes diseñados para lo mismo.

En cuanto a los resultados, como se puede apreciar Rust está codeándose con C y C++ en cuanto a resultados de velocidad. Por ese lado parece un lenguaje interesante si efectivamente alcanza esa velocidad siendo más seguro que C.

Quise probar algunos pequeños programillas, sólo por curiosidad e instalé primero, los paquetes de Rust que hay en OpenSuse, aunque en la documentación prefieren descargar (luego veremos ese método) los programas de su sitio web. De momento me centré en hacer unas pocas pruebas de compilación tomando tiempos con lo que tenía más a mano, cortando y pegando código encontrado en algunas páginas.

Después de esas pruebas, que ya habían aumentado mi curiosidad, también investigué la popularidad de Rust. Lo he encontrado en el puesto 26 en enero de 2021. Sí, sigue sin estar en la cúspide de la popularidad, pero tampoco está de los últimos. No es que me importe mucho la popularidad de un lenguaje cuando me planteo aprender uno. De hecho, como mis motivaciones no son curriculares o económicas, sino que sólo me muevo por la pura curiosidad, puedo elegir aquellos lenguajes que me resultan más llamativos o curiosos y así termino dedicándole horas a lenguajes como smalltalk, lisp, scheme, erlang... Todos lenguajes muy interesantes, pero poco populares o usados en proyectos.

Configurar el entorno de trabajo

El entorno de trabajo, como se puede imaginar cualquiera que haya leído un poco este blog, es Emacs. Pero vamos por partes.

Instalación de Rust

Como he dicho antes, en el manual de Rust realiza la instalación desde su sitio con un simple comando:

$  curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

En realidad, yo lo he hecho en dos pasos, ─ya me perdonaréis si peco de prudente─, pero no me fío de ejecutar un script bajado de la Internet así como así. Primero lo descargué y después de estudiar el script lo ejecuté:

$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf > sh.rustup.rs
$ bash sh.rustup.rs

El estudio del script, tampoco ha sido exhaustivo, al final lo que hace es descargar herramientas del sitio de Rust. Por lo menos comprobé que los enlaces son congruentes. Lo demás es confiar que esos ejecutables han sido compilados y se mantienen con las medidas de seguridad esperada. Al menos recomiendan forzar el uso de cifrado TLS en la conexión de descarga que la hace algo más segura. La confianza en este caso sería la misma que puedes depositar en los ejecutables que te bajas de cualquier repositorio de software libre.

Cuando ejecutas el script, aparece un menú preguntando el tipo de instalación con tres alternativas: instalación por defecto, avanzada o cancelar. Elegí la opción 1, la instalación por defecto. Después de unos momentos en que descargó los paquetes informó que la instalación ha tenido éxito. En la pantalla aparece un mensaje completo con información sobre el lugar donde se instala2. Hay que añadir ese directorio al $PATH. Una vez hecho basta comprobar si la instalación es correcta y funciona:

$ rustc --version
rustc 1.49.0 (e1884a8e3 2020-12-29)
$ cargo --version
cargo 1.49.0 (d00d64df9 2020-12-05)

Una vez instalado se puede actualizar la instalación con:

$ rustup update

o desinstalarlo con:

$ rustup uninstall

o llamar a la documentación con:

$ rustup doc

Como se puede apreciar un entorno completo.

Preparar el editor

Aquí llega el momento de instalar paquetes en Emacs y convertirlo en un auténtico IDE para Rust. No lo he probado mucho aún, pero lo que he visto me ha gustado. Los paquetes que he instalado son los siguientes:

  • rustic: proporciona el modo mayor de Emacs. Funciona sin lsp-mode pero si lo tienes instalado mejor.
  • flymake-rust: compilación sobre la marcha buscando errores y avisos que se pueden corregir antes de pedir la compilación.
  • ob-rust: para poder gestionar bloques de código Rust en org-mode.

La configuración que he puesto en mi init.el es muy sencilla. He añadido el paquete rustic al inicio:

(require 'rustic)

También, si usas lsp-mode hay que activar el hook para el lenguaje:

(add-hook 'rustic-mode-hook #'lsp)

Probando todo

Bueno pues vamos a por el Hola mundo correspondiente.

~ $ cd proyectos
~/proyectos $ mkdir hola-mundo
~/proyectos $ cd hola-mundo/
~/proyectos/hola-mundo $ emacs main.rs

en ese fichero el contenido será:

fn main() {
    println!("Hola, mundo!");
}

y compilamos

~/proyectos/hola-mundo $ rustc main.rs
~/proyectos/hola-mundo $ ./main
Hola, mundo!

y todo funciona perfectamente. Ahora a probar... ya iré contando cómo van las cosas.

Nota al pie de página:

1

Erlang Term Storage

2

Por defecto se instala en ~/.cargo/bin

Categoría: rust erlang emacs

Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en Mastodon y en Diaspora con el nick de Notxor.

También se ha abierto un grupo en Telegram, para usuarios de esa red.

Disculpen las molestias.