
Aprender un nuevo lenguaje de programación te va a costar más o menos dependiendo de la experiencia actual que tengas con otros. Algunos, se aprenden por acumulación, esto es, sumas sintaxis, sumas librerías, añades trucos, y practicando hasta la extenuación, claro está.
Y luego está Rust, que se aprende por sustitución. No porque sea caprichoso, sino porque te obliga a cambiar el modelo mental con el que llevas años resolviendo problemas, con algunos mecanismos del lenguaje que, reconozco, me han costado bastante digerir, hasta que en algún momento, algo te hace click y comprendes la razón de esos puntos que al principio parecen un tanto esotéricos, sobre todo si cometes el error de compararlos con otros lenguajes.
Si vienes de lenguajes interpretados y de muy alto nivel como Python o Javascript, Rust de va a sonar a chino, literalmente.
Si tu mundo es Java o C#, la curva de aprendizaje será, digamos, media.
Y por último, si conoces C++, tu aprendizaje de Rust será algo natural, aunque también te requerirá cierto esfuerzo.
Al inicio de mi carrera profesional (hace más años de lo que recuerdo), comencé programando en C++, de modo que cuando me acerqué a Rust hace unos años, de alguna manera pensé que me resultaría ameno y hasta atractivo, pero no fue así: comencé leyendo el libro clásico Programming Rust mas toda la documentación de la web del lenguaje (muy buena, por cierto), pero me quedé bloqueado en varias ocasiones, después de optar a un proyecto en WebAssembly con Rust (que al final no salió).
No obstante, siempre volvía a Rust y a su ecosistema, más que nada por curiosidad y por la escalabilidad en mis escarceos en infraestructuras HPC (high performance computing), en donde ahorrar hasta el último byte y ciclo de cpu son importantes.
Y de ahí este artículo, cuya intención es principalmente insistir en los conceptos que debes asumir y comprender muy bien desde el principio de Rust para no estrellarte, hasta que comienzas a ver el bosque completo.
Hoy día siento cierta fluidez con Rust, es más, diría que hasta entusiasmo. Rust no te pregunta “¿cómo quieres programar?”. Rust te pregunta “¿qué garantías estás dispuesto a declarar?”. Y en esta pregunta, que tiene cierto tono filosófico, está la razón por la que cuesta tanto al principio… y por la que, cuando por fin te iluminas, ya no miras igual ni el lenguaje ni el resto de su ecosistema.
Siempre he odiado las comparaciones infantiles entre lenguajes y entornos: cada uno tiene su sitio y su nicho, y nuestra habilidad es saber elegir qué entornos, tecnologías, etc. son las más adecuadas según cada proyecto (y su ciclo de vida futuro).
Este artículo, a modo de brújula, es una forma de nombrar lo que suele doler en Rust cuando vienes de otros lenguajes y que a mí me hubiese gustado encontrar al comienzo.
En cierto modo, Rust no es una nueva sintaxis, sino una especie de nuevo pacto al programar, muy diferente de otros lenguajes aunque, obviamente, tenga sus connotaciones similares con C++.
1) Rust no es C++, ni Java, ni Python (aunque tenga ciertos parecidos a elementos concretos de todos ellos)
Es tentador clasificar Rust por proximidad o similitud a otros lenguajes:
- “Se parece a C++ porque compila y es rápido”.
- “Se parece a Java/C# porque tiene traits que recuerdan a interfaces”.
- “Se parece a Python en la ergonomía moderna y el tooling”.
Y sin embargo, si lo abordas desde esas comparaciones, Rust se te queda como una maqueta: se parece por fuera, pero por dentro no encaja.
En C++ el poder se negocia con convenciones, disciplina (no te olvides de los "delete") y una cultura de haz lo correcto, pero nada, salvo tu buen hacer, te obliga a hacer lo correcto. En Rust, como veremos, esto es diferent.
En Java/C# la memoria y la vida de los objetos se externaliza a un runtime y el poderoso recolector de memoria que tango te aligera programar y despreocuparte del ciclo de vida de variables y recursos. En Python, por su parte, el coste está en la ejecución, no en la compilación, te centras en lo funcional y el lenguaje mismo te da ciertas garantías, pero todo tiene un coste, claro.
Rust, en cambio, te da la primera en la frente al mover una parte importante de la complejidad de una aplicación al momento de compilarla. Sí, has leído bien: me costó mucho captar esta idea y su razón.
En otros lenguajes, muchos asuntos internos de una aplicación, de bajo nivel, te lo dan todo hecho, pero Rust es mucho más explícito, y esto duele al principio (y luego te enamoras precisamente de esta cualidad que abordas y fluye de forma natural y productiva).
2) El uso de la memoria no es un detalle interno: es una parte del diseño
La intención principal de Rust es evitar bugs antes de que aparezcan en ejecución (lagunas de memoria, data races, punteros tontos, accesos no permitidos, etc.), y para eso, para muchos tipos de errores, tienes que preocuparte explícitamente de cómo tu aplicación usa internamente la memoria. Pero tranquilo, que, en realidad, esto es más fácil de lo que suena.
En muchos lenguajes, la gestión de la memoria ni aparece por ningún lado: declaras una variable o instancias un objeto, lo usas, y luego te olvidas, y un recolector de basura o garbage collector (que ni te enteras de que existe), hace su trabajo. Pero para ganar esta fluidez a la hora de escribir código, hay que pagar un precio, bien en la forma de uso ineficiente de la memoria y falta de optimización o bien con la introducción de errores que darán en algún momento la cara.
En Rust, la memoria se vuelve parte de la arquitectura. Esto me costó mucho comprenderlo (después de estar muy viciado con C# y Javascript con Node).
En realidad, esto es una ventaja… si aceptas algo chocante al comienzo:
- No existe un recolector de basura que amortigüe el coste de decisiones vagas.
- La vida de los datos no es una consecuencia: es un contrato. Puff, otro concepto que digerir.
Esto es, explícitamente hay que indicar para qué vas a usar un variable o recurso, cómo y cuál va a ser su ciclo de vida en la aplicación.
Esta es la primera gran fricción: en Rust, el lenguaje te pregunta “¿quién es el dueño de esto?”, y lo hace porque quiere garantizar algo muy concreto: que su liberación sea segura, determinista y eficiente, y que se haga lo antes posible para liberar recursos. Esto se consigue directamente por la misma naturaleza del lenguaje.
Para ello, se introduce el concepto de propiedad (ownership), mover (move) y préstamo (borrow) de variables y recursos:
Ownership, en realidad, es un concepto muy simple, por el que (simplificando un poco):
- Cada valor tiene un único dueño.
- Cuando el dueño sale de scope (contexto), el valor se libera, y punto; esto es, los recursos que utiliza (memoria) es liberada inmediatamente.
- El valor puede cambiar de dueño (lo que se denomina "mover").
- Al prestar un valor, no se transfiere su propiedad.
Y ya está, ideas simples pero con un impacto poderoso.
En el siguiente snippet se puede ver todo lo anterior (y creéme, al principio es muy frustrante ver cómo para unas simples líneas de código, el compilador no para de protestar hasta que corrijes lo que está mal):
fn main() {
let s = String::from("El Libro Negro del Programador"); // s es el dueño de esa cadena de texto
let t = s; // move: ahora t es el dueño
println!("{}", t);
println!("{}", s); // error: s ya no tiene el valor, el compilador protesta
}
Pero lo importante es que ese error en el código, se detecta en compilación, no en ejecución, y esto tiene un impacto extraordinario en la seguridad y eficiencia de tu aplicación.
Cuando el compilador de Rust indica que está todo bien, te garantiza que formalmente, el código es correcto, al menos desde el punto de vista de data races, asignaciones erróneas de valores, y mil cosas más.
3) Mutabilidad y aliasing: cuando "&mut" es una promesa de exclusividad
Si vienes de lenguajes donde puedes tener múltiples referencias y mutar casi en cualquier sitio, Rust te parecerá extraño y hasta hostil. Pero la regla clave es elegante cuando la comprendes:
- Puedes tener muchas referencias inmutables ("&T") a un mismo valor.
- Solo puede existir una referencia mutable a un valor ("&mut T").
Para los que conocen C o C++, les sonará "&", pero en Rust no existe el concepto de "puntero" (pointer), es tan solo indicar una referencia a un valor y el derecho de leerlo o cambiarlo.
Esto parece un obstáculo hasta que lo miras como lo que realmente es: un principio de diseño para eliminar data races y estados intermedios ilegales que pueden provocar errores catastróficos al ejecutar la aplicación.
Lo improtante es que Rust te obliga a declarar cuándo algo puede cambiar, y la consecuencia natural de esto es que no solo se ayuda al compilador, sino también al lector del código a comprenderlo y, también, al diseño de la aplicación.











Rafael Gómez Blanes/-/logos/img/hdl-plataforma.png)











