Un pequeño vistazo al lenguaje de programación de sistemas Rust
Introducción
En esta entrada quiero contar mi experiencia usando el lenguaje de programación Rust. Está dirigido principalmente a mis amigos que saben que uso Rust y me han preguntado por qué.
Considero que con tener idea del funcionamiento de lenguajes como C, C++ o Java es suficiente para poder entender este artículo.
Breve descripción de Rust
Rust es un lenguaje de programación iniciado en Mozilla que se orienta en construir software eficiente y seguro. Además de ser muy usado en Mozilla, diversas compañías lo usan para producir software comercial y para aumentar el rendimiento de un servicio que ofrece dicha compañía, entre muchos otros casos de uso.
Rust tiene varios aspectos propios que lo distinguen sustancialmente de otros lenguajes de programación; algunos los mencionaré en este artículo. Igualmente Rust toma inspiración de otros lenguajes de programación. Es considerado también por muchos programadores como el lenguaje más querido.
Hablar de Rust, entre muchas otras cosas, es hablar de su poderoso compilador, de sus reglas de propiedad de variables y de su fuerte sistema de tipado; características que logra manejar de forma que lo distingue de otros lenguajes.
Qué paradigma usa Rust
No puedo catalogar Rust en un único paradigma pues utiliza influencia de muchos otros lenguajes muy diversos. Solo puedo decir que el sincretismo entre tantos elementos de distintas fuentes está muy bien manejado: todo se amolda como piezas de un muy cuidado rompecabezas.
Dónde aprender Rust
Se puede empezar desde la sección de aprendizaje de la página oficial de Rust.
Considero que El libro es una excelente herramienta para aprender Rust pues es exhaustivo en las características de Rust y a la vez sencillo de leer.
Tengo que hacer una mención especial a la forma de enseñanza de El libro, el cual utiliza la teoría para aplicarla en distintos ejemplos de código y en los dos proyectos que simulan casos de uso reales:
- Una pequeña implementación del programa
grep:minigrep. - Un servidor HTTP básico.
También se explica en El libro por qué algunas soluciones que parecen correctas pueden no funcionar en Rust. En estos casos se modifican esas soluciones para adaptarlas a la forma de trabajar en Rust.

En esta sección de El libro se modifica un programa que utiliza el patrón de diseño State, que se basa en conceptos de programación orientada a objetos, para aprovechar mejor las fortalezas de Rust.
Primera impresión: complejo.
Tengo que ser realista: trabajar con Rust es frustrante muchas veces, no porque sea un lenguaje malo, sino porque el compilador exige al programador que siga varias reglas que en un nivel novicio pueden no entenderse del todo. En este momento puedo recordar algunas de las herramientas que ofrece Rust:
- Las reglas de propiedad de variables (
ownership), que se aseguran de que el programa usa la memoria correctamente. - El tiempo de vida de una variable y cómo se relacionan con otros tiempos
de vida (
lifetime), también para gestionar la memoria correctamente. - Los comportamientos compartidos entre distintos tipos de datos,
definiendo la relación entre estos tipos (
traits, similar a las interfaces de lenguajes como Java). - El uso de mutabilidad interna para variables inmutables (módulo
cellde la librería estándar).
Estas son poderosas herramientas que poseen diversas reglas para asegurar que su uso es correcto, reglas que no siempre son sencillas de aplicar aunque alguien pueda tener mucho tiempo usando Rust.
Rust no es un lenguaje fácil. Su rigurosidad en la consistencia del programa, por ejemplo, al trabajar con tipos de datos genéricos y con sus comportamientos se me asemeja mucho al lidiar con problemas del campo de las matemáticas, sin mencionar que Rust posee características propias que ocasiona que varios paradigmas de programación propios de otros lenguajes de programación se necesiten implementar con algunos cambios para aprovechar Rust al completo.
El compilador como protagonista en el proceso de desarrollo
Si hay algo que se nota al empezar a programar con Rust es que el compilador
ejerce un protagonismo activo al momento de desarrollar; tanto así que,
además de señalar qué partes del código son incorrectas, ya sea por sintaxis o
incluso por semántica, muchas veces logra dar consejos de qué hacer para
corregir estos errores, lo que lo convierte en una especie de tutor. De hecho,
el compilador oficial, rustc, contiene una opción llamada --explain que
permite tener una descripción del porqué no se puede expresar el código de
cierta manera.
Ya que uno de los objetivo del compilador y del lenguaje en general es el de crear sistemas con un mayor nivel de seguridad que en C o C++ en aspectos como el manejo de la memoria o de la concurrencia, el compilador no se puede dar el lujo de aceptar cualquier código como válido. Para los principiantes eso se traduce en una mayor probabilidad de recibir errores en la compilación, pero a la larga, con mucha constancia, permite mejorar mucho la forma en que se programa.
El compilador cuenta también con capacidades de hacer test development drive (TDD). En El libro se anima a desarrollar usando esta herramienta.
Si bien, en un principio, el compilador puede parecer como una suerte de ente malévolo cuyo principal propósito es el de frustrar al programador, la realidad es totalmente distinta: Una vez conociendo más la forma de trabajar en Rust y cómo funcionan las reglas que lo rigen, se va comprendiendo qué es lo que el compilador quiere decir. Cuando esto me ocurrió, terminé viendo al compilador de Rust como un gran aliado para evitar errores comunes de código. En otras palabras, aprendí a confiar más en el compilador, un lujo que no me puedo dar con los compiladores de C y C++, por ejemplo.
Empoderamiento: una premisa de Rust
Aun con los inconvenientes que se pueden presentar a la hora de programar en Rust, quiero tomar prestado una frase del prefacio de El libro en una traducción libre:
No siempre estuvo tan claro, pero el lenguaje de programación Rust es fundamentalmente sobre el empoderamiento: no importa qué tipo de código estés escribiendo ahora, Rust te da poder para llegar más lejos, para programar con confianza en una variedad más amplia de dominios de los que tenías antes.
Personalmente, este poder lo he experimentado de las siguientes formas:
-
Cuando utlizo algo de la librería estándar o de los paquetes comunitarios (crates en el argot "rustificado"). Me recuerda a Python en su útil librería estándar y en PyPI.
-
En las muy variadas formas que tengo para expresar mi lógica. En Rust he usado elementos de programaciones estructuradas, orientados a objetos y funcionales.
-
En la libertad de elegir. Rust se centra en ofrecer muy buenos niveles de rendimiento y de seguridad en el código; sin embargo, puedo perder algo de comodidad y de versatilidad debido a que las reglas para ofrecer rendimiento y seguridad son estrictas. Aun así, Rust también me da la capacidad de cambiar perder algo de rendimiento o de relajar un poco las buenas prácticas por comodidad. Lo bueno es que muchas veces esta pérdida de rendimiento es insignificante. Algunos ejemplos de esta libertad de elección son:
-
El método
clone, que hace una copia profunda de una variable. Lo puedo usar para relajar un poco las rígidas pero claras normas de propiedad de variables de Rust, perdiendo a su vez un poco de rendimiento debido a la clonación. -
El método
unwrap, que me permite saltar algunas verificaciones en cuanto a si un procedimiento se ejecutó correctamente (el tipoResult<T, E>, siendoTyEtipos de datos genéricos) o en cuanto a si una variable contiene un valor o no contiene nada (el tipoOption<T>). Si bien estas situaciones se deberían abordar haciendo control de errores, este método es útil para hacer programación rápida dando por sentado que las entradas del programa son válidas.
-
-
En su sintaxis. Rust aquí no trata de innovar tanto; más bien, su sintaxis es muy similar a la de otros lenguajes de programación, lo que facilita su lectura.
-
En la vibrante comunidad tanto para pedir ayuda como para mostrar los proyectos personales hechos en Rust. Por otra parte, gracias a la comunidad, existen herramientas para varios IDE y editores de código que facilitan el desarrollo en Rust, aunque en este tema falta más desarrollo.
-
En el compilador, un valioso amigo en el proceso de desarrollo, en la seguridad de que mi programa no tiene algunos de los errores programación más comunes como usar memoria no inicializada gracias a que el compilador se encarga de esto y a la capacidad de desarrollar utilizando pruebas que me permitan comprobar desde el compilador que el código funciona correctamente, gracias al TDD.
¿Es Rust para mí?
Absolutamente sí.
Puede ser que usar Rust sea algunas veces complicado y frustrante incluso para programadores con experiencia. Pero con persistencia y la gran ayuda de la comunidad se pueden sortear estos obstáculos para hacer software seguro y eficiente.
A lo largo de mi aprendizaje con El libro he planeado varios proyectos
personales que, considero, Rust es una perfecto lenguaje para llevarlos a cabo.
Uno de ellos es mi implementación de cat de Unix:
ax-cat. Estoy preparando un artículo
describiendo los inconvenientes que se me presentaron en el desarrollo y cómo
los solucioné.
Un pequeño ejemplo de código en Rust
Esta sección no pretende ser un manual de Rust; es decir, no explicaré aquí elementos como su sintaxis. A pesar de que sé que el lector no conozca nada de Rust, doy por sentado que el código es lo suficientemente claro como para dar una idea de su funcionamiento por sí mismo.
Haré un pequeño ejemplo usando mutabilidad interna, la cual es una característica de Rust usada para, por ejemplo, modificar elementos específicos de una estructura. Pero antes tengo que explicar brevemente cómo funciona la mutabilidad en Rust:
Una variable en Rust es, por defecto, inmutable. Esto significa que, una vez
declarada, no puede modificarse su valor. Si deseo modificar el valor de una
variable tengo que declararla explícitamente como mutable utilizando la
palabra reservada mut. En el caso de las estructuras, los elementos de una
variable serán todos inmutables o mutables.
Para una mejor entendimiento de mutabilidad en Rust, está esta sección de El libro.
Considere el siguiente ejemplo:
struct Person {
name: String,
age: u8,
}
fn main() {
let my_first_person = Person {
name: String::from("John Doe"),
age: 32,
};
my_first_person.age += 1;
let mut my_second_person = Person {
name: String::from("Bob McDoe"),
age: 26,
};
my_second_person.age += 1;
}
Aquí creo una estructura llamada Person que posee dos miembros:
name: Es el nombre de una persona. Es un String.age: Es la edad de una persona. Es un entero de ocho bits (unsigned 8).
En fn main() creo dos variables de tipo Person asigando sus respectivos
nombres y edades. También trato de incrementar la edad de las dos personas.
La sintaxis de este programa es muy similar a la de C, lo que facilita su
comprensión.
Ahora, cuando trato de compilar este programa, obtengo el siguiente error:
error[E0594]: cannot assign to `my_first_person.age`, as `my_first_person` is not declared as mutable
--> src/main.rs:12:5
|
7 | let my_first_person = Person {
| --------------- help: consider changing this to be mutable: `mut my_first_person`
...
12 | my_first_person.age += 1;
| ^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign
error: aborting due to previous error
For more information about this error, try `rustc --explain E0594`.
¿Cuál es el problema?
El compilador, al encontrarse con que la variable my_first_person no es
mutable, decide no compilar el programa cuando trato de modificar la variable
directamente. Mientras tanto, con la variable my_second_person esto es
permitido pues fue declarada mutable. Tengo que elogiar al compilador pues,
en este caso, ha sido capaz de mostrarme el error y de sugerirme una posible
solución.
Entonces, para poder modificar my_first_person es necesario que sea mutable.
Ahora bien, supongamos que es necesario que el miembro name de Person sea
inmutable y que el miembro age sea mutable. Nos encontramos con el siguiente
problema:
- Declarando
my_first_personcomo mutable:nameyageson mutables. - Declarando
my_first_personcomo inmutable:nameyageson inmutables.
En este caso, no puedo usar el consejo del compilador pues solamente quiero que
age se pueda modificar. Tengo que usar otra solución, y la mutabilidad
interna mediante la estructura RefCell se presenta como un candidato perfecto.
Entonces procedo a modificar el programa para usarlo con RefCell.
use std::cell::RefCell;
struct Person {
name: String,
age: RefCell<u8>,
}
fn main() {
let my_first_person = Person {
name: String::from("John Doe"),
age: RefCell::new(32),
};
// La siguiente sentencia no funcionará pues la variable no es mutable
//my_first_person.name = String::from("Peter Doe");
*my_first_person.age.borrow_mut() += 1;
let mut my_second_person = Person {
name: String::from("Bob McDoe"),
age: RefCell::new(26),
};
my_second_person.name = String::from("Bob Doe");
*my_second_person.age.borrow_mut() += 1;
}
Probablemente se pueda pensar que la sintaxis para usar RefCell es un poco
extraña, pero una vez que se tenga más tiempo en Rust se hará más fácil entender
el sentido de esta sintaxis que se usa con tipos de datos genéricos, algo
que no explicaré aquí. En este caso, solamente es necesario tener en cuenta que
age: RefCell<u8> significa que age contiene una estructura RefCell que
aloja un dato de tipo u8, y que borrow_mut() toma una referencia mutable del
valor contenido en RefCell que puedo usar para cambiar ese valor interno.
Los cambios realizados para adaptar el programa al uso de RefCell permiten
lograr el objetivo de ser capaz de cambiar un elemento de Person en una
variable que no es mutable. Además, añadí una sentencia comentada para ilustrar
que el miembro name de my_first_person no se puede modificar directamente
mientras que en my_second_person sí se puede modificar sin inconvenientes.
En resumen, lo que ocurrió al momento del desarrollo fue que:
- El compilador me mostró el error y me dio un consejo de cómo solucionarlo.
- Pude elegir la solución del compilador, pero decidí usar RefCell, una estructura parte de la librería estándar de Rust, para lograr mi objetivo. Libertad de elección.
El compilador acepta el programa como correcto. ¡Excelente!
Conclusión
Rust es un lenguaje de programación que busca mejorar el proceso de construcción de software de múltiples formas, todas fundamentadas en ofrecer capacidades y libertades que en otros lenguajes no son posibles. A pesar de que en Rust las cosas se manejan distinto de otros lenguajes, obligando a pensar un poco más las decisiones de diseño de un programa, considero que el esfuerzo vale la pena en un intento de mejorar la calidad del programa en construcción.
Desde mi punto de vista personal, considero que Rust me ha ayudado a tener más confianza en el código que escribo, a estructurar mejor el funcionamiento de mis programas y a aprender muchos conceptos y formas de trabajar nuevas para mí.
Por supuesto, existen más aspectos de Rust que pueden ayudar a mejorar el proceso de construcción de software de los que aquí he expuesto. Recomiendo nuevamente leer El libro para conocerlos.