Primeros pasos en Rust - Conceptos Básicos del Lenguaje

Apuntes de Rust - Parte 1


Qué es Rust?

Rust es un lenguaje de programación moderno que se centra en el rendimiento, la seguridad de la memoria y la concurrencia. Desarrollado por Mozilla Research, Rust combina características de lenguajes de programación de sistemas, como C y C++, con mecanismos de seguridad y abstracciones de alto nivel.

Entre las fortalezas de Rust, tenemos:

  1. Seguridad de memoria: Rust se destaca por su enfoque en la seguridad de memoria sin necesidad de un recolector de basura. Utiliza el concepto de borrowing and ownership para garantizar que no haya errores de acceso a memoria, como desreferencias nulas o lecturas después de liberar memoria. El sistema de tipos de Rust, combinado con su sistema de préstamo, permite detectar y prevenir errores de seguridad y corrupción de memoria en tiempo de compilación.

  2. Concurrencia confiable: Rust facilita la escritura de código concurrente seguro. El modelo de concurrencia de Rust se basa en la idea de que los datos deben ser mutables o compartidos, pero no ambas cosas al mismo tiempo. Esto evita problemas comunes de concurrencia, como condiciones de carrera y bloqueos inseguros.

  3. Rendimiento: Proporciona control preciso sobre la asignación de memoria y el uso de recursos, y su modelo de propiedad permite que el compilador realice optimizaciones agresivas sin sacrificar la seguridad. Además, Rust facilita el uso de punteros y manipulación directa de memoria cuando sea necesario.

  4. Abstracciones de alto nivel: Además de su enfoque en la programación de sistemas, Rust sorprende al brindar una amplia gama de características de alto nivel. Estas abstracciones de nivel superior permiten a los desarrolladores escribir código de manera más eficiente y concisa, sin comprometer la seguridad ni el rendimiento. Algunas de estas características incluyen una biblioteca estándar robusta, soporte para programación orientada a objetos y funcional, patrones de concurrencia de alto nivel como los actores y mecanismos de manejo de errores más sofisticados, como el manejo de resultados y opciones.

  5. Legibilidad y mantenibilidad: Rust promueve la escritura de código limpio y modular. Su sintaxis está diseñada para ser expresiva y fácil de entender, y su compilador proporciona mensajes de error detallados y útiles.

Rust es una alternativa poderosa para desarrollar sistemas seguros, eficientes y mantenibles, especialmente en entornos donde se requiere un control cercano del hardware y se prioriza la seguridad y el rendimiento.


Instalación de Rust

Considero que el sitio oficial de Rust proporciona instrucciones claras para realizar la instalación del compilador de Rust. Recomiendo seguir las indicaciones detalladas en la URL del sitio oficial para realizar la instalación de Rust.

Para validar si la instalación es correcta, ejecute el siguiente comando:

rustup --version

Si no desea instalar el compilador, o no desea configurar un ambiente de desarrollo, puede utilizar Replit, una alternativa ideal para explorar Rust dado que provee el ambiente de ejecución y el IDE.


Cargo

Cargo es el sistema de construcción y administración de paquetes oficial utilizado en Rust. Es una herramienta de línea de comandos que se instala automáticamente cuando se instala Rust.

Cargo simplifica el proceso de desarrollo en Rust al automatizar tareas comunes, como la compilación de código, la gestión de dependencias y la ejecución de pruebas.

Características y funciones de Cargo:

  1. Construcción y compilación: Cargo se encarga de compilar código Rust de manera eficiente y gestionar todas las dependencias necesarias para un proyecto (realiza compilación incremental, lo que acelera el proceso de compilación en proyectos grandes).

  2. Gestión de dependencias: Cargo facilita la gestión de las dependencias de un proyecto. Puede especificar las dependencias necesarias en el archivo Cargo.toml y Cargo se encargará de descargar e instalar automáticamente las versiones correctas de esas dependencias.

  3. Creación de proyectos: Cargo puede crear nuevos proyectos en Rust utilizando el comando cargo new. Esto generará la estructura de directorios y archivos básicos de un proyecto, incluyendo un archivo Cargo.toml para administrar las dependencias y la configuración del proyecto.

  4. Ejecución de pruebas: Cargo proporciona soporte para escribir y ejecutar pruebas automatizadas en Rust. Puedes agregar pruebas a tu proyecto en el directorio tests y luego ejecutarlas utilizando el comando cargo test.

  5. Construcción y ejecución de proyectos: Cargo simplifica la compilación y ejecución de un proyecto por medio de los comandos cargo build y cargo run. Estos comandos se encargan de compilar código y generar ejecutables.


Hola Mundo, Rust

El comando cargo es una herramienta poderosa que simplifica la construcción y ejecución de proyectos en Rust. Al ejecutar cargo new, se encarga de crear un nuevo proyecto en Rust. Creemos un proyecto llamado proyecto1:

cargo new proyecto1
cd proyecto1

Al interior de proyecto1, encontramos los siguientes directorios y archivos:

Al interior del directorio src, encontremos un archivo main.rs, donde .rs es la extensión de los archivos fuente de Rust. Cargo por defecto escribe el archivo main.rs con el típico Hola Mundo. Ingrese al directorio src desde su IDE y abra el archivo main.rs.

La función main() es el punto de entrada para todos los programas de Rust. En este caso, simplemente muestra el mensaje “¡Hola, mundo!”, en la salida estándar utilizando la instrucción println!(). La palabra reservada fn se utiliza para definir una función en Rust.

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

Los bloques de código en Rust se definen entre llaves, similar a C o Java, y las líneas de código terminan en punto y coma, igual que en estos lenguajes mencionados.

Guarde el archivo, el comando cargo build compilará el programa y el comando cargo run lo ejecutará. Si el código es correcto, debería imprimirse el mensaje “¡Hola, mundo!”, impreso en la terminal.

cargo build
cargo run

Variables - Conceptos Básicos

En Rust, puedes definir variables utilizando la palabra clave let. La sintaxis básica para definir una variable es la siguiente:

let <nombre_de_la_variable> = <valor_de_la_variable>;

Algunos ejemplos de variables definidas en Rust:

let cadena = "Esto es una cadena";  // cadena (string)
let numero_entero = 17;  // entero (integer)
let numero_flotante = 1.75;  // flotante (float)
let valor_booleano = true;  // booleano (boolean)

Rust es un lenguaje de programación fuertemente tipado. Rust admite la especificación explícita del tipo de variable. Puedes hacerlo de la siguiente manera:

let cadena : &str = "Esto es una cadena";  // referencia a una cadena (string)
let numero_entero : u32 = 17;  // entero sin signo de 32 bits (unsigned integer)
let numero_flotante : f64 = 1.75;  // flotante de doble precisión (double precision float)
let valor_booleano : bool = true;  // booleano (boolean)

En estos ejemplos, se utiliza la sintaxis :tipo después del nombre de la variable para especificar el tipo de forma explícita. Esto puede ser útil, ser explícito acerca del tipo de la variable o cuando no se puede inferir automáticamente el tipo.

let <nombre_de_la_variable> : <tipo> = <valor_de_la_variable>

Nota: En Rust, por defecto, las variables son inmutables, lo que significa que no se pueden modificar después de su asignación inicial. Ejemplo:

let numero_entero : u32 = 17;  // entero sin signo de 32 bits (unsigned integer)

numero_entero = 18;

Esto genera el error:

error[E0384]: cannot assign twice to immutable variable `numero_entero`
 --> src\main.rs:3:5
  |
2 |     let numero_entero : u32 = 17;
  |         ----------
  |         |
  |         first assignment to `numero_uno`
  |         help: consider making this binding mutable: `mut numero_entero`
3 |     numero_entero= 18;
  |     ^^^^^^^^^^^^^^^ cannot assign twice to immutable variable

Si desea que una variable sea mutable, debe agregar la palabra clave mut antes del nombre de la variable, como se muestra a continuación:

let mut numero_entero : u32 = 17;  // entero sin signo de 32 bits (unsigned integer)

numero_entero = 18;

Rust se caracteriza por la seguridad y la prevención de errores, por lo que tiene reglas estrictas sobre el manejo de variables y su mutabilidad.


Ejemplo

En este programa, se definen dos variables, con sus valores iniciales respectivamente. Luego se realiza la suma de ambas variables y se almacena en la variable suma.

Finalmente, con println!() se imprime el resultado de la suma en pantalla. El valor de las variables se muestran utilizando {} como indicador de posición dentro de la cadena.

fn main() {
    let mut numero_1 : i32 = 50;
    let mut numero_2 : i32 = 50;
    let mut suma : i32 = 0;

    suma = numero_1+ numero_2;

    println!("El resultado de la suma de {} + {} es: {}", numero_1, numero_2, suma);
}

Para compilar puede omitir el comando cargo build y ejecutar directamente la instrucción cargo run. cargo run, compilara el programa si el código fuente sufrió cambios y ejecutara el binario generado en el proceso de compilación, si este fue exitoso.

cargo run

Nota: Para este escenario recibirá unos mensajes de advertencia (warnings) indicando que no es necesario que numero_1 y numero_2 sean variables mutables.

Ejemplo:

warning: variable does not need to be mutable
 --> src\main.rs:2:9
  |
2 |     let mut numero_1 : i32 = 50;
  |         ----^^^^^^^^
  |         |
  |         help: remove this `mut`
  |
  = note: `#[warn(unused_mut)]` on by default

Algunos tipos de datos en Rust

Esta tabla muestra algunos ejemplos de los tipos de datos más comunes en Rust. Sin embargo, Rust ofrece otros tipos de datos y la capacidad de definir sus propias estructuras y tipos de datos personalizados utilizando el sistema de tipos de Rust y las estructuras de datos de la biblioteca estándar.

Tipo de datoDescripciónEjemplo
i8Entero con signo de 8 bitslet entero: i8 = 42;
i16Entero con signo de 16 bitslet entero: i16 = -1000;
i32Entero con signo de 32 bits (valor predeterminado)let entero: i32 = 1000000;
i64Entero con signo de 64 bitslet entero: i64 = -1234567890;
u8Entero sin signo de 8 bitslet entero: u8 = 255;
u16Entero sin signo de 16 bitslet entero: u16 = 65535;
u32Entero sin signo de 32 bitslet entero: u32 = 4294967295;
u64Entero sin signo de 64 bitslet entero: u64 = 18446744073709551615;
isizeEntero con signo (tamaño de puntero)let entero: isize = 42;
usizeEntero sin signo (tamaño de puntero)let entero: usize = 42;
f32Número de punto flotante de 32 bitslet flotante: f32 = 3.14;
f64Número de punto flotante de 64 bitslet flotante: f64 = 3.14;
boolValor booleanolet booleano: bool = true;
charCarácter Unicode de 4 byteslet caracter: char = 'a';
&strCadena de texto inmutablelet cadena: &str = "Hola, mundo!";
StringCadena de texto mutablelet cadena: String = String::from("Hola");
()Tipo vacío o unidadlet vacio: () = ();
Option<T>Valor opcional que puede ser Some(T) o Nonelet opcion: Option<i32> = Some(42);
Result<T, E>Valor que representa un resultado exitoso o errorlet resultado: Result<i32, String> = Ok(42);

Referencias

  • The Rust Programming Language. 2nd Edition by Steve Klabnik and Carol Nichols, with contributions from the Rust Community. 2023

Did you find this article valuable?

Support Jesse Padilla by becoming a sponsor. Any amount is appreciated!