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:
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.
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.
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.
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.
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:
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).
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.
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.
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.
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 dato | Descripción | Ejemplo |
i8 | Entero con signo de 8 bits | let entero: i8 = 42; |
i16 | Entero con signo de 16 bits | let entero: i16 = -1000; |
i32 | Entero con signo de 32 bits (valor predeterminado) | let entero: i32 = 1000000; |
i64 | Entero con signo de 64 bits | let entero: i64 = -1234567890; |
u8 | Entero sin signo de 8 bits | let entero: u8 = 255; |
u16 | Entero sin signo de 16 bits | let entero: u16 = 65535; |
u32 | Entero sin signo de 32 bits | let entero: u32 = 4294967295; |
u64 | Entero sin signo de 64 bits | let entero: u64 = 18446744073709551615; |
isize | Entero con signo (tamaño de puntero) | let entero: isize = 42; |
usize | Entero sin signo (tamaño de puntero) | let entero: usize = 42; |
f32 | Número de punto flotante de 32 bits | let flotante: f32 = 3.14; |
f64 | Número de punto flotante de 64 bits | let flotante: f64 = 3.14; |
bool | Valor booleano | let booleano: bool = true; |
char | Carácter Unicode de 4 bytes | let caracter: char = 'a'; |
&str | Cadena de texto inmutable | let cadena: &str = "Hola, mundo!"; |
String | Cadena de texto mutable | let cadena: String = String::from("Hola"); |
() | Tipo vacío o unidad | let vacio: () = (); |
Option<T> | Valor opcional que puede ser Some(T) o None | let opcion: Option<i32> = Some(42); |
Result<T, E> | Valor que representa un resultado exitoso o error | let 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