Introducción técnica a Rust

Rust

Uno de nuestros mejores desarrolladores, Álvaro Mateos, nos introduce a Rust y nos cuenta algunas curiosidades que pueden resultar de interés.

Es un artículo recomendable tanto para aquellos desarrolladores que se plantean aprender Rust, como para aquellos técnicos curiosos que quieren estar al tanto de las novedades tecnológicas.

Es una primera aproximación al lenguaje con algunos ejemplos, ventajas y desventajas. A pesar de que hay muchos datos contrastados con fuentes oficiales, hay otras partes que son completamente subjetivas y rebatibles. Creemos que esto es enriquecedor para alguien que esté leyendo sobre el tema, pero no por ello tratamos de escribirlo como dogma, ya que nuestra intención es que sirva de introducción al lenguaje.

Mejor sea darle paso directamente a Álvaro.

 

¿Qué es Rust?

Rust es un lenguaje de programación muy reciente, cuya primera versión surge en 2015. A pesar de que soporta programación orientada a objetos, no es para lo que está pensado inicialmente, ya que su fuerza reside en la programación funcional.

Este lenguaje de programación lo crea Mozilla Firefox en busca de una herramienta más eficiente que les ayudara al desarrollo de su navegador y acaba siendo publicado por su eficiencia.

Rust nos dará buenos resultados tanto para hacer sistemas distribuidos, aplicaciones web, sistemas operativos o navegadores web.

Algunas características

Puedes realizar un desarrollo tanto en el lado del cliente como del servidor

Es multiplataforma y multi-sistemas operativos

Cuenta con una sintaxis muy parecida a C y C++

Se basa principalmente en la velocidad y el rendimiento (lo vemos después)

Aporta seguridad y evita, en tiempo de compilación, posibles errores durante la ejecución como por ejemplo de concurrencia

 

Entrando un poco más en materia

Me gustaría hacer foco en las partes más destacables del lenguaje y que más me han llamado la atención. Algo muy significativo es que, según el informe Stackoverflow, Rust es el lenguaje más querido por los programadores durante los últimos 3 años, muy por encima de C++, o de los populares Java y Javascript.

Por otra parte, Rust aporta un sistema propio de construcción de paquetes llamado Cargo, muy similar a npm o Maven, del cual se puede aprovechar muchas librerías propias y funciones externas que son de lo más útil.

A pesar de que es un lenguaje reciente, a día de hoy está siendo usado por Firefox, Dropbox, RedHat, Reddit, Azure, Twitter o Deno.

Twitter por ejemplo está migrando su aplicación, y el creador de NodeJS, al percatarse de los problemas existentes que han ido surgiendo, creó una versión con Rust, denominándola Deno.

Una vez destacado estos puntos, hay que decir, que como todo, nada es perfecto y menos en el desarrollo. Bajo mi punto de vista, Rust también tiene algunos puntos flacos, mencionando especialmente la curva de aprendizaje. Al menos en mi caso, siendo programador senior Java Back-end, Rust es muy diferente a lo que estoy acostumbrado, y bajo mi punto de vista, creo que para alguien con un know-how similar al mío, puede ser más complicado de aprender.

Teniendo en cuenta que Rust es muy parecido a C++, comprendería que esta dificultad sea inexistente para alguien con ese bagaje. No obstante, y todo sea dicho, escribir C de forma correcta no es tampoco una tarea sencilla.

¿Y por qué Rust? Pues quizá el siguiente gráfico pueda ayudarnos a entenderlo. Abajo podemos ver tres lenguajes, C/C++, Java y Python. Y están cruzados con las variables control y seguridad.

Es decir, si trabajamos con  C/C++ se puede ver que contamos con mucho control, pero la seguridad es bastante baja, que es justo lo contrario que tenemos con Python.

¿Y dónde se posiciona Rust? Rust aporta tanto control, como seguridad, tanto en el código como en la gestión de los recursos como por ejemplo la memoria.

En caso de que no haya quedado claro, con control me refiero a que por ejemplo en C/C++ puedes tener funciones de bajo nivel más próximos al hardware, ya sea gestión de memoria, hilos etc. Si quieres asignar una parte de la memoria, puedes hacerlo manualmente, pero ¿qué implica? que la seguridad se ve afectada debido al potencial error humano y que es necesario un conocimiento del lenguaje para liberar los recursos ya utilizados.

Como decíamos, con Rust tienes mucho control y mucha seguridad. ¿Y cómo es posible? Esto se logra gracias a estos tres aspectos, que son el core del lenguaje:

-Ownership

-Borrowing

-References

Puesto que esto es algo un poco complicado de explicar y existen varias referencias que lo describen, como la página oficial de Rust, me gustaría contarlo con mis propias palabras, y al final dejar un esquema resumen más técnico.

Ownership

Este concepto consiste en que cada variable es dueña del dato que tiene y se define como owner. Cuando en una variable se pierde el owner del valor o se sale del scope donde se ha definido, deja de estar disponible  y Rust la  borra automáticamente. De esta manera el desarrollador puede despreocuparse de que pueda quedarse almacenada en memoria o quede algún recurso desaprovechado.

Esto tiene una relación directa con el Garbage Collector (GC). Debido al sistema descrito anteriormente, en Rust no existe. Comparando con otros lenguajes, en C por ejemplo, tienes que controlar el GC manualmente a través de las sentencias, Malloc y Free, y Java lo gestiona automáticamente mediante un proceso periódico que evalúa cuando una variable no tiene una referencia a un valor.

Ideas clave Ownership

-Cada valor tiene una variable denominada owner

-Solo puede haber un owner a la vez por valor

-Cuando el owner está fuera del scope el valor se borra

-Si una variable no tiene owner deja de estar disponible

 

References and Borrowing

Los conceptos de References and Borrowing son muy similar a los punteros de C++, algo que personalmente siempre me han parecido complejos de controlar. De forma muy resumida, utilizaremos punteros en Rust cuando queremos acceder al dato de la variable sin “mover” el owner.

Quizá, la mejor forma de entenderlo sea con el siguiente ejemplo:

En el primer cuadro aparece un código que funciona. 1 se asigna a a, y a se asigna a b. En este primer caso, pintaría por pantalla:

Variable a 1

Variable b 1

 

El segundo caso, es el mismo que el primero, pero lo que se asigna es un String a la variable c, y d = c. En este caso, el código fallaría, ya que para Rust el tipo String es un tipo especial y lo que hace es que cuando asignas d = c, c, el owner se mueve y pierde el dueño de este dato (Ownership), por lo que, al tratar de imprimir c, esta variable ya no está disponible. Al compilar el código vemos el siguiente error:

 

 

Para algunos tipos especiales de Rust, como es String y otros tipos de datos más, hacen falta punteros, haciendo uso del carácter &.

 

En el tercer caso sí que funciona, ya que lo que especificamos al usar el puntero, es “tomar prestado” el valor sin mover el owner. Pintaría por pantalla lo siguiente:

 

variable c Aurigae

variable d Aurigae

 

Destacar en este punto, como se puede ver en la imagen anterior, es que Rust, aporta trazas de errores muy descriptivas y sencillas de comprender, lo que reduce el tiempo de análisis del error y la resolución del mismo.

 

 

Seguridad y Velocidad

 

A continuación, expongo algunos de los motivos más destacables por los que hacen que Rust sea un lenguaje basado en la seguridad y la velocidad:

 

Todas las comprobaciones relacionadas con el código se hacen en tiempo de compilación: O coste en tiempo de ejecución.

-Verifica que el código es correcto, y si hay algo que está mal lo indicará en el momento de compilar el proyecto.

-Previene errores de uso de memoria, de concurrencia y comportamientos indefinidos mediante la aplicación de un sistema interno que procesa el código estático.

-Utilizas lo necesario en cada momento. El compilador obliga a definir recursos cuando es estrictamente necesario. Por ejemplo, te indica cuando necesitas usar Mutex.

Sintaxis

Os dejo algunas funciones comunes que personalmente me llamaron la atención cuando leía sobre Rust. Me parece una buena forma de familiarizarse con el lenguaje.

 

Imprimir texto:

 

println!(“Hola, {}!”, “mundo”);

println!(“variable: {}”, var1);

Variables:

Por defecto inmutables. Como se ha mencionado al inicio del artículo, Rust soporta principalmente programación funcional, por lo que las variables inmutables son un de los conceptos básicos de este paradigma. Si es necesario que una variable sea mutable, hay que especificarlo explícitamente mediante la sentencia mut.

 

 let a = 7;

let mut b = 5;

 

Constantes

 

 const A: i32 = 7;

 

Booleanos

 

 

let x = true;

let y: bool = false;

 

 

Tipos de datos existentes en Rust

 

 

Enteros: i8, i16, i32, i64

Enteros sin signo: u8, u16, u32, u64

Enteros que dependen de la arquitectura: isize, usize

Float: f32, f64

 

Por defecto, si no se especifica el tipo, las variables son de tipo i32 o f64:

 let x = 42;

let y = 1.0;

let y: f32 = 3.0; // f32

Tupla

 

Tupla es un tipo de Array. La diferencia como se puede ver en el ejemplo, es que se pueden definir elementos de diferente tipo de dato:

 

 

TUPLA ARRAYS
let tup: (i32, f64, u8) = (500, 6.4, 1);

let tup = (500, 6.4, 1);

let a = [1, 2, 3];

let mut m = [1, 2, 3];

 

 

 let (x, y, z) = tup;

println!(“The value of y is: {}”, y);

 

En el ejemplo anterior, al hacer let de la tupla, se asigna automáticamente los valores del array a las variables del let de cada posición. En el caso de que el número de variables del let no coincida con el número de elementos de la tupla, la compilación fallará.

 

 

Bucles

 

loop {

println!(“¡Otra vez!”);

}

 

for i in 0..5 {

println!(“{}”, a[i]);

}

 

for i in a.iter() {

println!(“{}”, i);

}

 

while i < 5 {

println!(“{}”, a[i]);

i = i + 1;

}

 

 

 

Me resultó muy curioso que exista algo concreto para generar un bucle infinito, pero puede ser útil para montar algo en un servidor que tenga que estar siempre ejecutándose o por ejemplo, utilizarlo en una función para generar el entorno de un videojuego.

Como se ha mencionado anteriormente Rust es similar a C++. Sin embargo tiene sus diferencias. Aquí expongo en forma de tabla las diferencias que según mi parecer son más destacables:

 

C++ vs Rust

 

 

NULL Es posible crear referencias NULL No es posible inicializar con NULL
Concurrencia Manual. Necesario una buena programación y conocimiento para evitar errores Sistema que en tiempo de ejecución detecta y evita estos errores, y obliga a usar Mutex cuando es necesario
Inicialización variables Tipos primitivos de variables tienen valores indefinidos cuando no se inicializa Todas la variables deben ser explícitamente inicializadas

 

Para empezar

 

Os recomiendo que lo probéis, aunque sea para jugar un poco.

 

Sin instalación, para todos los sistemas operativos y online: http://bit.ly/2B12t68

 

Con instalación, para Linux y Mac: curl https://sh.rustup.rs -sSf | sh

 

Con instalación para Windows:

 

  • RUSTUP-INI.exe
  • Visual Studio 2017 Community (C++)

 

 

Y algunos ejemplos:

Para comenzar un proyecto una vez instalado Rust en local hay que realizar los siguientes pasos:

 

  • Desde una terminal ejecutamos el comando cargo new ejemplo-rust
  • Una vez generado el directorio ejemplo-rust con la estructura básica de un proyecto Rust, accedemos a él
  • Accedemos al fichero Cargo.toml si nuestro proyecto requiere alguna librería propia del repositorio Cargo
  • Por último, lanzamos el comando cargo run desde el directorio padre para ver el resultado por la pantalla

 

Como ejemplo podéis probar el siguiente código en el main.rs. Hay que incluir previamente la librería ferris-says = “0.1” en Cargo.toml

 

extern crate ferris_says;

 

use ferris_says::say;

use std::io::{ stdout, BufWriter };

 

fn main() {

let out = b”Ahora os toca a vosotros!!”;

let width = 28;

 

let mut writer = BufWriter::new(stdout());

say(out, width, &mut writer).unwrap();

}

 

 

Al ejecutarlo tendremos la mascota de Rust!

 

 

Si te ha gustado este artículo, te recomendamos este otro sobre Spring Framework que también escribió Álvaro Mateos.