Manual Completo de Rust

Este manual está diseñado para desarrolladores que quieren aprender Rust desde cero, con un enfoque práctico para entornos laborales y sistemas Linux.

1. Introducción a Rust

Rust es un lenguaje de programación de sistemas que combina:

1.1. Características principales

1.2. Casos de uso en entornos laborales

2. Instalación en Linux

2.1. Instalación básica

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env

2.2. Verificar instalación

rustc --version
cargo --version

2.3. Configuración para desarrollo profesional

# Instalar herramientas adicionales
rustup component add rustfmt clippy rust-analysis rust-src

# Configurar variables de entorno (añadir a ~/.bashrc o ~/.zshrc)
export RUST_BACKTRACE=1
export CARGO_HOME="$HOME/.cargo"
export PATH="$CARGO_HOME/bin:$PATH"

2.4. Configurar el editor (VS Code recomendado)

# Instalar extensiones para VS Code
code --install-extension rust-lang.rust-analyzer
code --install-extension bungcip.better-toml
code --install-extension serayuzgur.crates

3. Fundamentos del lenguaje

3.1. Sintaxis básica

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

3.2. Variables y mutabilidad

let x = 5;          // Inmutable
let mut y = 10;     // Mutable
const PI: f64 = 3.14159;  // Constante

3.3. Tipos de datos

Categoría Tipos
Escalares i8, i16, i32, i64, i128, isize
u8, u16, u32, u64, u128, usize
f32, f64
bool, char
Compuestos Tuplas: (i32, f64, char)
Arrays: [i32; 5]

3.4. Control de flujo

If-Else
let number = 6;

if number % 4 == 0 {
    println!("divisible por 4");
} else if number % 3 == 0 {
    println!("divisible por 3");
} else {
    println!("no divisible por 4 o 3");
}
Match (similar a switch)
let value = 42;

match value {
    1 => println!("uno"),
    2 | 3 | 5 | 7 | 11 => println!("número primo"),
    13..=19 => println!("adolescente"),
    _ => println!("sin coincidencia"),
}

3.5. Bucles

Loop, While, For
// Loop infinito
loop {
    println!("siempre");
    break; // Rompe el bucle
}

// While
let mut x = 0;
while x < 10 {
    x += 1;
}

// For
for i in 1..10 {  // Rango 1-9
    println!("{}", i);
}

// Iterar sobre array
let arr = [10, 20, 30];
for element in arr.iter() {
    println!("el valor es: {}", element);
}

4. Ownership y Borrowing

Concepto único de Rust que garantiza seguridad de memoria en tiempo de compilación.

4.1. Reglas de Ownership

  1. Cada valor en Rust tiene una variable llamada su dueño (owner).
  2. Solo puede haber un dueño a la vez.
  3. Cuando el dueño sale del ámbito, el valor se descarta.
Ejemplo de Ownership
let s1 = String::from("hola");  // s1 es el dueño
let s2 = s1;                    // s2 ahora es el dueño, s1 ya no es válido
// println!("{}", s1);          // Error! s1 ya no es válido

4.2. Borrowing (préstamos)

Permite referencias a un valor sin tomar ownership.

Referencias inmutables
let s1 = String::from("hola");
let len = calculate_length(&s1);  // Prestamos s1 como referencia

fn calculate_length(s: &String) -> usize {
    s.len()
}
Referencias mutables
let mut s = String::from("hola");
change_string(&mut s);

fn change_string(s: &mut String) {
    s.push_str(", mundo");
}

Reglas de Borrowing:

  1. Puedes tener una referencia mutable o varias inmutables, pero no ambas al mismo tiempo.
  2. Las referencias deben ser siempre válidas (no pueden apuntar a memoria liberada).

5. Estructuras y Enums

5.1. Estructuras (struct)

Definición e implementación
struct Usuario {
    nombre: String,
    email: String,
    edad: u32,
    activo: bool,
}

impl Usuario {
    // Constructor
    fn new(nombre: String, email: String, edad: u32) -> Self {
        Self {
            nombre,
            email,
            edad,
            activo: true,
        }
    }
    
    // Método
    fn desactivar(&mut self) {
        self.activo = false;
    }
}

// Uso
let mut usuario = Usuario::new(
    String::from("Ana"),
    String::from("ana@ejemplo.com"),
    30
);
usuario.desactivar();

5.2. Enumeraciones (enum)

Enums con datos
enum Mensaje {
    Salir,
    Mover { x: i32, y: i32 },
    Escribir(String),
    CambiarColor(i32, i32, i32),
}

impl Mensaje {
    fn llamar(&self) {
        // Implementación del método
    }
}

let msg = Mensaje::Escribir(String::from("hola"));

6. Gestión de errores

Rust no tiene excepciones. En su lugar usa los tipos Result y Option.

6.1. Result

Manejo básico de errores
use std::fs::File;

fn abrir_archivo() -> Result {
    let f = File::open("archivo.txt")?;  // Operador ? propaga errores
    Ok(f)
}

match abrir_archivo() {
    Ok(file) => println!("Archivo abierto"),
    Err(e) => println!("Error al abrir: {}", e),
}

6.2. Option

Manejo de valores opcionales
fn dividir(a: f64, b: f64) -> Option {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}

match dividir(10.0, 2.0) {
    Some(resultado) => println!("Resultado: {}", resultado),
    None => println!("No se puede dividir por cero"),
}

7. Colecciones

7.1. Vectores

Uso de Vec<T>
let mut numeros = vec![1, 2, 3];
numeros.push(4);
numeros.push(5);

for num in &numeros {
    println!("{}", num);
}

// Mapeo y filtrado (estilo funcional)
let cuadrados: Vec<_> = numeros.iter().map(|x| x * x).collect();
let mayores_a_2: Vec<_> = numeros.iter().filter(|&&x| x > 2).collect();

7.2. HashMaps

Diccionarios con HashMap<K, V>
use std::collections::HashMap;

let mut contactos = HashMap::new();
contactos.insert("Ana", "ana@ejemplo.com");
contactos.insert("Juan", "juan@ejemplo.com");

// Acceso seguro con match
match contactos.get("Ana") {
    Some(&email) => println!("El email de Ana es {}", email),
    None => println!("No se encontró a Ana"),
}

// Iteración
for (nombre, email) in &contactos {
    println!("{}: {}", nombre, email);
}

8. Programación concurrente

8.1. Hilos

Creación de hilos
use std::thread;
use std::time::Duration;

let handle = thread::spawn(|| {
    for i in 1..10 {
        println!("Hilo: {}", i);
        thread::sleep(Duration::from_millis(100));
    }
});

for i in 1..5 {
    println!("Principal: {}", i);
    thread::sleep(Duration::from_millis(200));
}

handle.join().unwrap();  // Esperar a que termine el hilo

8.2. Canales para comunicación entre hilos

MPSC (Multiple Producer, Single Consumer)
use std::sync::mpsc;
use std::thread;

let (tx, rx) = mpsc::channel();

thread::spawn(move || {
    let val = String::from("hola");
    tx.send(val).unwrap();
});

let recibido = rx.recv().unwrap();
println!("Recibido: {}", recibido);

8.3. Mutex para acceso compartido

Compartir datos entre hilos
use std::sync::{Arc, Mutex};
use std::thread;

let contador = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
    let contador = Arc::clone(&contador);
    let handle = thread::spawn(move || {
        let mut num = contador.lock().unwrap();
        *num += 1;
    });
    handles.push(handle);
}

for handle in handles {
    handle.join().unwrap();
}

println!("Resultado: {}", *contador.lock().unwrap());

9. Trabajo con archivos y E/S

9.1. Lectura y escritura de archivos

Ejemplo completo
use std::fs::{File, OpenOptions};
use std::io::{Read, Write, BufReader, BufWriter};

// Escribir en un archivo
let mut file = File::create("datos.txt").expect("No se pudo crear el archivo");
file.write_all(b"Hola, archivo!").expect("Error al escribir");

// Leer de un archivo
let mut file = File::open("datos.txt").expect("No se pudo abrir el archivo");
let mut contenido = String::new();
file.read_to_string(&mut contenido).expect("Error al leer");
println!("Contenido: {}", contenido);

// Añadir a un archivo existente
let mut file = OpenOptions::new()
    .append(true)
    .open("datos.txt")
    .expect("No se pudo abrir");
writeln!(file, "Más datos").expect("Error al escribir");

9.2. Trabajo con directorios

Operaciones con directorios
use std::fs;
use std::path::Path;

// Crear directorio
fs::create_dir("nuevo_dir").expect("No se pudo crear directorio");

// Verificar si existe
if Path::new("nuevo_dir").exists() {
    println!("El directorio existe");
}

// Listar contenido
for entrada in fs::read_dir(".").expect("Error al leer directorio") {
    let entrada = entrada.expect("Error al obtener entrada");
    println!("{}", entrada.path().display());
}

10. Cargo y gestión de proyectos

10.1. Estructura de un proyecto

mi_proyecto/
├── Cargo.toml      # Configuración del proyecto
├── src/
│   ├── main.rs     # Punto de entrada para binarios
│   ├── lib.rs      # Punto de entrada para bibliotecas
│   └── bin/        # Binarios adicionales
├── tests/          # Tests de integración
├── examples/       # Ejemplos de uso
└── target/         # Directorio de construcción (generado)

10.2. Cargo.toml

Ejemplo completo
[package]
name = "mi_proyecto"
version = "0.1.0"
edition = "2021"
authors = ["Tu Nombre "]
description = "Una breve descripción"
license = "MIT"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
reqwest = "0.11"

[dev-dependencies]
tempfile = "3.2"  # Dependencias solo para tests

[features]
default = []
http_client = ["reqwest"]

10.3. Comandos útiles de Cargo

Comando Descripción
cargo new Crear un nuevo proyecto
cargo build Compilar el proyecto
cargo run Compilar y ejecutar
cargo check Verificar código sin compilar
cargo test Ejecutar tests
cargo doc --open Generar y abrir documentación
cargo fmt Formatear código según convenciones
cargo clippy Ejecutar linter para mejores prácticas

11. Integración con sistemas Linux

11.1. Llamadas al sistema

Ejemplo: Obtener información del sistema
use std::process::Command;

fn main() {
    // Ejecutar comando de sistema
    let output = Command::new("uname")
        .arg("-a")
        .output()
        .expect("Fallo al ejecutar comando");
    
    if output.status.success() {
        let info = String::from_utf8_lossy(&output.stdout);
        println!("Info del sistema: {}", info);
    } else {
        let error = String::from_utf8_lossy(&output.stderr);
        eprintln!("Error: {}", error);
    }
}

11.2. Creación de demonios

Ejemplo básico de demonio
use std::{thread, time};
use std::fs::File;
use std::os::unix::process::CommandExt;
use std::process::{Command, Stdio};

fn daemonize() -> std::io::Result<()> {
    // Crear nuevo proceso
    let child = Command::new("/proc/self/exe")
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn()?;
    
    println!("Demonio ejecutándose con PID: {}", child.id());
    Ok(())
}

fn main() -> std::io::Result<()> {
    if std::env::args().any(|arg| arg == "--daemon") {
        // Código del demonio
        loop {
            // Registrar actividad (en producción usaría syslog)
            let mut file = File::create("/tmp/daemon.log")?;
            writeln!(file, "Demonio activo")?;
            
            thread::sleep(time::Duration::from_secs(5));
        }
    } else {
        // Lanzar como demonio
        daemonize()?;
    }
    
    Ok(())
}

11.3. Señales de Linux

Manejo de señales
use signal_hook::{iterator::Signals, SIGINT, SIGTERM};
use std::{thread, time::Duration};

fn main() -> Result<(), Box> {
    let signals = Signals::new(&[SIGINT, SIGTERM])?;
    
    thread::spawn(move || {
        for sig in signals.forever() {
            println!("Recibida señal: {:?}", sig);
            // Manejar cierre limpio
            std::process::exit(0);
        }
    });
    
    // Trabajo principal
    loop {
        println!("Trabajando...");
        thread::sleep(Duration::from_secs(1));
    }
}

12. FFI (Interfaz con funciones externas)

12.1. Llamar a código C desde Rust

Ejemplo con libc
// En Cargo.toml: libc = "0.2"

extern crate libc;

use libc::{c_int, c_void, size_t};

extern "C" {
    fn malloc(size: size_t) -> *mut c_void;
    fn free(ptr: *mut c_void);
    fn abs(num: c_int) -> c_int;
}

fn main() {
    unsafe {
        let ptr = malloc(100);
        if ptr.is_null() {
            panic!("malloc falló");
        }
        free(ptr);
        
        println!("Valor absoluto de -10: {}", abs(-10));
    }
}

12.2. Exponer funciones Rust a C

Crear una biblioteca para C
// En lib.rs
#[no_mangle]
pub extern "C" fn suma(a: i32, b: i32) -> i32 {
    a + b
}

// En Cargo.toml
[lib]
name = "misuma"
crate-type = ["cdylib"]  # Biblioteca dinámica para C

Compilar con cargo build --release y usar el .so generado desde C.

13. Ejemplos avanzados para entornos laborales

13.1. Servicio web con Actix

API REST básica
// En Cargo.toml: actix-web = "4.0"

use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};

#[get("/")]
async fn hola() -> impl Responder {
    HttpResponse::Ok().body("Hola mundo!")
}

#[post("/saludar")]
async fn saludar(info: web::Json<Saludo>) -> impl Responder {
    HttpResponse::Ok().json(info.into_inner())
}

#[derive(serde::Deserialize, serde::Serialize)]
struct Saludo {
    nombre: String,
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(hola)
            .service(saludar)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

13.2. Cliente de base de datos con SQLx

Conexión a PostgreSQL
// En Cargo.toml: 
// sqlx = { version = "0.6", features = ["postgres", "runtime-tokio-native-tls"] }
// tokio = { version = "1.0", features = ["full"] }

use sqlx::{PgPool, postgres::PgPoolOptions};
use std::env;

#[derive(Debug, sqlx::FromRow)]
struct Usuario {
    id: i32,
    nombre: String,
    email: String,
}

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let database_url = env::var("DATABASE_URL")
        .unwrap_or("postgres://usuario:contraseña@localhost/basedatos".to_string());
    
    let pool = PgPoolOptions::new()
        .max_connections(5)
        .connect(&database_url)
        .await?;
    
    // Consulta simple
    let usuarios = sqlx::query_as::<_, Usuario>("SELECT id, nombre, email FROM usuarios")
        .fetch_all(&pool)
        .await?;
    
    println!("Usuarios: {:?}", usuarios);
    
    // Inserción con parámetros
    let nuevo_usuario = sqlx::query!(
        "INSERT INTO usuarios (nombre, email) VALUES ($1, $2) RETURNING id",
        "Ana",
        "ana@ejemplo.com"
    )
    .fetch_one(&pool)
    .await?;
    
    println!("Nuevo usuario ID: {}", nuevo_usuario.id);
    
    Ok(())
}

13.3. Procesamiento paralelo con Rayon

Procesamiento de datos en paralelo
// En Cargo.toml: rayon = "1.5"

use rayon::prelude::*;

fn main() {
    let datos = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // Procesamiento paralelo
    let resultados: Vec<_> = datos.par_iter()
        .map(|x| x * x)
        .filter(|x| x % 2 == 0)
        .collect();
    
    println!("Resultados: {:?}", resultados);
    
    // Reducción paralela
    let suma_cuadrados: i32 = datos.par_iter()
        .map(|x| x * x)
        .sum();
    
    println!("Suma de cuadrados: {}", suma_cuadrados);
}

14. Buenas prácticas y optimización

14.1. Consejos para código profesional

14.2. Optimización de rendimiento

15. Recursos adicionales

15.1. Libros y documentación

15.2. Herramientas para Linux

Consejo profesional: Configura un pipeline de CI (GitHub Actions, GitLab CI) para ejecutar tests, clippy y rustfmt en cada commit. Esto mantendrá tu código Rust de alta calidad.

Conclusión

Rust es un lenguaje poderoso para desarrollo de sistemas con un enfoque en seguridad y rendimiento. Su curva de aprendizaje puede ser empinada debido a conceptos como ownership y borrowing, pero una vez dominados, permiten escribir código seguro y eficiente sin sacrificar productividad.

En entornos laborales, Rust es ideal para:

En Linux, Rust ofrece ventajas significativas sobre otros lenguajes para desarrollo de sistemas, herramientas de línea de comandos y servicios de red.