Por qué aprender Rust
En The pragmatic programmer se recomienda aprender un lenguaje nuevo de programación al año. No puedo decir que lo haga todos los años, pero éste, estoy aprendiendo Rust y es el objeto de este artículo explicar por qué me he decidido por este lenguaje.
Rust es un lenguaje de bajo nivel, de la familia de C o C++, pero pensado para nuestros tiempos. Eso quiere decir que es muy rápido y que su destino es construir sistemas operativos, sistemas embebidos en los que ejecutarse con un suspiro de recursos, hacer cálculos intensivos y llevarse bien con otros lenguajes para auxiliar en los casos en los se necesita eficiencia y velocidad.
¿Por qué mola?
Ahhh, la erótica de la programación... que solo entendemos ingenieros y programadores 😉. Rust tiene una aproximación moderna a los lenguajes de bajo nivel, es rápido, es seguro por diseño, es sencillo de aprender para mi en comparación con C/C++ y me ha seducido frente a sus posibles alternativas por cosas que te cuento a continuación.
Rust es rápido
El lenguaje más rápido en ejecución sigue siendo C con Gcc. Piensa que se creó en los años 70, y sigue manteniendo este honorable título. Desde entonces los procesadores se han hecho cada vez más potentes pero lenguajes como C siguen siendo necesarios en tareas complejas de tiempo real. Por ejemplo Ableton Live, el software DAW del que tanto escribo en este blog, está escrito en C++.
Por otro lado el mundo del machine learning tambien ha hecho necesaria la utilidad de la velocidad y la eficiencia. Por ejemplo Python se apoya en librerías con código C, C++ como NumPy, scikit-learn o TensorFlow y PyTorch.
Entonces, la velocidad y la gestión de recursos aun importan, y puestos a elegir en 2024 un lenguaje rápido que ocupe el nicho de C/C++, los contendientes son D, Go, Zig, Carbon, Swift y Rust. De estos lenguajes, descarto Swift porque trabajo con el cada día y en velocidad no se acerca tanto si miras el repositorio que puse antes. Tambien descarto Carbon porque es más reciente y experimental y por tanto la utilidad práctica es menor.
Asi que me queda D, Go, Zig y Rust. Entre estas opciones, D es el más veterano y el más veloz, por detrás tenemos en orden Rust (Nightly) y luego los demás con cierta diferencia. Obviamente la versión nightly de rust no es estable, pero la estable está en el mismo orden de desempeño que Go y Zig. Por tanto, en cuanto a velocidad todos estos lenguajes son muy competentes y son otras características las que te pueden decantar por uno u otro.
Rust es seguro
C es el más rápido de la clase, pero es relativamente sencillo hacer algo mal y generar un crash. Ahí es dónde un lenguaje moderno puede hacerse un hueco.
Rust utiliza el concepto de ownership, borrowing y lifetimes para garantizar la seguridad de la memoria en tiempo de compilación sin necesidad de un recolector de basura. En Rust, cada valor tiene un único "dueño" (owner). Cuando el dueño sale de alcance, el valor se libera automáticamente. Además, Rust permite el préstamo temporal de valores (borrowing) y la creación de referencias que pueden ser mutables o inmutables. Esto permite que Rust garantice la ausencia de condiciones de carrera, fugas de memoria y acceso a datos inválidos.
Esto es guay, y elegante diría yo, pero los contendientes tambien tienen ideas para gestionar la memoria originales, excepto D que se parece mucho a C++, así que acabamos de dejar un lado a D porque yo quiero una aproximación novedosa que aprender.
Zig permite al programador controlar directamente la asignación y liberación de memoria. No tiene recolector de basura y no usa un sistema de cuentas de referencias como Swift. En su lugar, proporciona herramientas para trabajar con la memoria como punteros con restricciones de alcance, manipulación de punteros nulos de forma segura y detección de desbordamiento de memoria en tiempo de ejecución. Es decir suena un poco a lo de siempre.
Go utiliza un enfoque de recolección de basura (garbage collection) para gestionar la memoria de manera automática. En realidad Go introduce características innovadoras en términos de concurrencia y paralelismo (goroutines y canales) pero no tanto en el modelo de gestión de memoria.
Por tanto, en este punto, gana de nuevo Rust porque su filosofía de gestión de memoria me parece una idea que no había explorado y por tanto amplía mi base de conceptos y ofrece seguridad a la hora de gestionar la memoria de forma elegante.
La sintaxis del lenguaje importa
Un lenguaje acaba por tener una estética y una legibilidad que viene dada por la sintaxis y dependiendo del caso y tu contexto, se parecerá o no a un lenguaje que ya conoces y puede resultarte más fácil de aprender. Vamos a verlo con un ejemplo, comparando una función muy tonta que dependiendo de tu edad y tu nombre, devuelve una cadena que dice si puedes votar.
Primero en Go
package main
import "fmt"
func verificarEdad(nombre string, edad int) string {
if edad >= 18 {
return fmt.Sprintf("Hola %s, eres mayor de edad", nombre)
} else {
return fmt.Sprintf("Hola %s, aún tienes que crecer para votar", nombre)
}
}
func main() {
nombre := "Jose"
edad := 20
mensaje := verificarEdad(nombre, edad)
fmt.Println(mensaje)
}
Luego en Zig
const std = @import("std");
fn verificarEdad(nombre: []const u8, edad: u32) ![]const u8 {
if (edad >= 18) {
return try std.mem.format("Hola {}, eres mayor de edad\n", .{nombre});
} else {
return try std.mem.format("Hola {}, aún tienes que crecer para votar\n", .{nombre});
}
}
pub fn main() void {
const nombre = "Jose";
const edad = 20;
const mensaje = try verificarEdad(nombre, edad);
std.debug.print(mensaje);
}
Y por último en Rust
fn verificar_edad(nombre: &str, edad: u32) -> String {
if edad >= 18 {
return format!("Hola {}, eres mayor de edad", nombre);
} else {
return format!("Hola {}, aún tienes que crecer para votar", nombre);
}
}
fn main() {
let nombre = "Jose";
let edad = 20;
let mensaje = verificar_edad(nombre, edad);
println!("{}", mensaje);
}
Vemos que hay ciertas características:
- Go y Rust no requieren parentesis para las condiciones if, como Swift
- Zig y Rust usan puntos y coma al final de cada linea
- Rust usa como convención snake_case, mientras que go y zig usan CamelCase, eso es más extraño
- Go usa := para asignaciones, como Pascal... que tiempos aquellos, cuando estudiaba en la universidad. No es algo que me produzca nostalgia.
- Rust indica el tipo de la función con una flecha como Swift
- Rust usa let para definir constantes como Swift
Bueno... resumiendo Rust se parece bastante a Swift que es un lenguaje que ya conozco, incluso soporta generics de una forma parecida. Es decir, dentro de la novedad de conceptos, me va a resultar muy familiar. Y tiene más ventajas para mi, porque se puede usar para construir aplicaciones móviles multiplataforma, pero de eso hablaré en la siguiente sección.
Campos de aplicación
Podemos decir que hay una serie de campos lógicos dónde Rust es útil y son todos relacionados con sistemas. Serían los siguientes:
- Utillería de sistemas: cli apps, compiladores, scripts de shell...
- Programación de red
- Sistemas operativos
- Software embebido, IoT (Internet of things)
Algunos de ellos no son muy comunes como crear un sistema operativo y no son una necesidad para la mayoría de los mortales, otros se pueden cubrir con otros lenguajes de scripting como hacer utilidades.
Desarrollo de juegos
Rust se abre camino en un espacio en el que C++ era la primera opción. Hay varios frameworks de desarrollo de juegos escritos en Rust como Bevy o Amethyst, y libros que te introducen a Rust en el contexto del desarrollo de juegos.
Aplicaciones móviles
La otra gran caja está en la colaboración con otros lenguajes. Rust se puede usar en aplicaciones móviles nativas para crear una base de código compartida entre iOS y Android. Esta idea me parece fascinante porque la alternativa hasta ahora era usar Kotlin Native, que está bien pero obliga al desarrollador de iOS a pasarse al bando contrario... y si es cierto que Kotlin es parecido a Swift, no podemos decir que sea más rápido en ejecución, y mucho menos más rápido que Rust. Es decir, me parece una forma democrática y atractiva de tener una capa de dominio y datos común en un proyecto nativo. Un ejemplo puede ser el toolkit uniffi-rs.
Web
Lo raro comienza cuando vemos que Rust tiene cierto predicamento entre desarrolladores web. Por ejemplo está de moda la integración de Rust en aplicaciones web a través Web Assemblies. Un Web assembly es un binario que permite a un programa escrito con otro lenguaje ejecutarse en un navegador web.
También hay frameworks para desarrollar backend como Rocket que casi te permiten ser full stack en Rust, aunque en mi opinión hay herramientas más productivas si no tienes requisitos extremos de velocidad o rendimiento.
Data Science y Machine Learning
Este es un campo natural para Rust, en cuanto a necesidades de procesamiento de datos. Lo voy a explorar aunque se que probablemente Python ya te ofrece todo lo que necesitas.
Desarrollo de audio
Y claro, una de las cosas que se hacen normalmente en C++ o C por las necesidades de tiempo real y concurrencia, son las aplicaciones de audio, como DAWs o plugins. Por tanto, una de las cosas que investigaré es como está este campo en Rust, pero para esto queda mucho.
Conclusiones
Tenía varias opciones para elegir un lenguaje que aprender: tengo curiosidad por Clojure desde hace mucho tiempo, Erlang me parece también interesante, pero la realidad es que ninguno de los dos tenía aplicaciones realistas en mi día a día.
Rust tiene varias, principalmente la de las aplicaciones móviles, pero además ocupa el espacio de lenguajes de bajo nivel que no tenía en mi caja de herramientas. Yo aprendí C en la universidad y llegué a usarlo en mi primer trabajo pero lo he olvidado por completo, y aunque Swift es rápido y puede llegar a ocupar el espacio de este tipo de lenguajes, no tiene esa vocación.
Por último tenemos que hay gente haciendo aplicaciones muy interesantes con Rust, por ejemplo la app de terminal Warp o algunos IDEs de programación. En concreto Zed me parece una apuesta minimalista muy interesante y lo uso habitualmente como editor alternativo a xCode.
Todo esto hace Rust muy interesante, y creo que en combinación con Python y Swift aporta una base conocimientos útiles para cubrir desarrollo web, móvil, sistemas y de data science o machine learning.