diff --git a/src/content/1.introduccion.md b/src/content/1.introduccion.md index e45bacd..07194b3 100644 --- a/src/content/1.introduccion.md +++ b/src/content/1.introduccion.md @@ -1,5 +1,5 @@ --- -nextPath: "porque-rust" +nextPath: "configuracion-entorno" previousPath: "" editor: false order: 1 diff --git a/src/content/10.structs.md b/src/content/10.structs.md new file mode 100644 index 0000000..ee5ad45 --- /dev/null +++ b/src/content/10.structs.md @@ -0,0 +1,730 @@ +--- +nextPath: "enums-pattern-matching" +previousPath: "borrowing" +editor: true +order: 10 +--- + +# Structs: Datos Estructurados en Rust + +Las structs nos permiten crear tipos de datos personalizados agrupando valores relacionados. Son fundamentales para organizar y encapsular datos en Rust, similar a las clases en otros lenguajes pero sin herencia. + +## Definiendo Structs + +### Struct Básica + +```rust +// Definir una struct +struct Usuario { + nombre: String, + email: String, + edad: u32, + activo: bool, +} + +fn main() { + // Crear una instancia + let usuario1 = Usuario { + email: String::from("alguien@ejemplo.com"), + nombre: String::from("Juan Pérez"), + activo: true, + edad: 25, + }; + + // Acceder a los campos + println!("Nombre: {}", usuario1.nombre); + println!("Email: {}", usuario1.email); + println!("Edad: {}", usuario1.edad); + println!("¿Activo? {}", usuario1.activo); +} +``` + +### Structs Mutables + +```rust +struct Contador { + valor: i32, + nombre: String, +} + +fn main() { + let mut contador = Contador { + valor: 0, + nombre: String::from("Mi Contador"), + }; + + println!("Valor inicial: {}", contador.valor); + + // Modificar campos (struct debe ser mutable) + contador.valor += 1; + contador.nombre = String::from("Contador Actualizado"); + + println!("Valor actualizado: {}", contador.valor); + println!("Nombre actualizado: {}", contador.nombre); +} +``` + +## Sintaxis de Construcción + +### Field Init Shorthand + +```rust +fn construir_usuario(email: String, nombre: String) -> Usuario { + Usuario { + email, // Equivale a email: email + nombre, // Equivale a nombre: nombre + activo: true, + edad: 18, + } +} + +fn main() { + let usuario = construir_usuario( + String::from("maria@ejemplo.com"), + String::from("María García") + ); + + println!("Usuario creado: {}", usuario.nombre); +} +``` + +### Struct Update Syntax + +```rust +fn main() { + let usuario1 = Usuario { + email: String::from("original@ejemplo.com"), + nombre: String::from("Usuario Original"), + activo: true, + edad: 30, + }; + + // Crear nuevo usuario basado en el anterior + let usuario2 = Usuario { + email: String::from("nuevo@ejemplo.com"), + nombre: String::from("Usuario Nuevo"), + ..usuario1 // Toma los valores restantes de usuario1 + }; + + println!("Usuario 2 - Edad: {}", usuario2.edad); // 30 + println!("Usuario 2 - Activo: {}", usuario2.activo); // true + + // Nota: usuario1 ya no es válido si algún campo movido no implementa Copy + // println!("{}", usuario1.nombre); // ERROR si String no implementa Copy +} +``` + +## Tipos de Structs + +### Tuple Structs + +Structs que se comportan como tuplas con nombres: + +```rust +// Tuple structs +struct Color(i32, i32, i32); +struct Punto(i32, i32, i32); + +fn main() { + let negro = Color(0, 0, 0); + let origen = Punto(0, 0, 0); + + // Acceso por índice como tuplas + println!("Rojo: {}", negro.0); + println!("Verde: {}", negro.1); + println!("Azul: {}", negro.2); + + // Destructuring + let Color(r, g, b) = negro; + println!("RGB: ({r}, {g}, {b})"); + + // Los tipos son diferentes aunque tengan la misma estructura + // let error: Color = origen; // ERROR: expected Color, found Punto +} +``` + +### Unit Structs + +Structs sin campos, útiles para traits: + +```rust +struct AlwaysEqual; + +fn main() { + let subject = AlwaysEqual; + let another = AlwaysEqual; + + // Útiles para implementar traits sin datos + println!("Unit structs creadas"); +} +``` + +## Implementando Métodos + +### Métodos Básicos + +```rust +#[derive(Debug)] +struct Rectangulo { + ancho: u32, + alto: u32, +} + +impl Rectangulo { + // Método que toma &self (referencia inmutable) + fn area(&self) -> u32 { + self.ancho * self.alto + } + + // Método que toma &mut self (referencia mutable) + fn duplicar_tamaño(&mut self) { + self.ancho *= 2; + self.alto *= 2; + } + + // Método que toma self (toma ownership) + fn destruir(self) -> String { + format!("Rectángulo {}x{} destruido", self.ancho, self.alto) + } + + // Método con parámetros adicionales + fn puede_contener(&self, otro: &Rectangulo) -> bool { + self.ancho >= otro.ancho && self.alto >= otro.alto + } +} + +fn main() { + let mut rect1 = Rectangulo { + ancho: 30, + alto: 50, + }; + + println!("Área: {}", rect1.area()); + + rect1.duplicar_tamaño(); + println!("Después de duplicar: {:?}", rect1); + + let rect2 = Rectangulo { + ancho: 10, + alto: 40, + }; + + println!("¿rect1 puede contener rect2? {}", rect1.puede_contener(&rect2)); + + // Método que consume la instancia + let mensaje = rect1.destruir(); + println!("{mensaje}"); + // rect1 ya no es válido después de destruir() +} +``` + +### Funciones Asociadas (Associated Functions) + +```rust +impl Rectangulo { + // Función asociada (como método estático) + fn nuevo(ancho: u32, alto: u32) -> Rectangulo { + Rectangulo { ancho, alto } + } + + // Crear un cuadrado + fn cuadrado(tamaño: u32) -> Rectangulo { + Rectangulo { + ancho: tamaño, + alto: tamaño, + } + } + + // Constantes asociadas + const RECTANGULO_UNITARIO: Rectangulo = Rectangulo { ancho: 1, alto: 1 }; +} + +fn main() { + // Llamar funciones asociadas con :: + let rect1 = Rectangulo::nuevo(10, 20); + let cuadrado = Rectangulo::cuadrado(5); + let unitario = Rectangulo::RECTANGULO_UNITARIO; + + println!("Rectángulo: {:?}", rect1); + println!("Cuadrado: {:?}", cuadrado); + println!("Unitario: {:?}", unitario); +} +``` + +### Múltiples Bloques impl + +Puedes tener múltiples bloques `impl` para la misma struct: + +```rust +struct Persona { + nombre: String, + edad: u32, +} + +impl Persona { + fn nueva(nombre: String, edad: u32) -> Persona { + Persona { nombre, edad } + } + + fn saludar(&self) { + println!("¡Hola! Soy {}", self.nombre); + } +} + +// Segundo bloque impl para la misma struct +impl Persona { + fn cumpleaños(&mut self) { + self.edad += 1; + println!("¡Feliz cumpleaños! Ahora tienes {} años", self.edad); + } + + fn es_adulto(&self) -> bool { + self.edad >= 18 + } +} + +fn main() { + let mut persona = Persona::nueva(String::from("Ana"), 17); + + persona.saludar(); + println!("¿Es adulto? {}", persona.es_adulto()); + + persona.cumpleaños(); + println!("¿Es adulto ahora? {}", persona.es_adulto()); +} +``` + +## Ownership y Structs + +### Structs con Referencias + +```rust +// Struct que contiene una referencia necesita lifetimes (lo veremos más adelante) +struct Producto<'a> { + nombre: &'a str, + precio: f64, +} + +fn main() { + let nombre_producto = String::from("Laptop"); + + let producto = Producto { + nombre: &nombre_producto, + precio: 999.99, + }; + + println!("Producto: {} - ${}", producto.nombre, producto.precio); +} +``` + +### Structs que Poseen Sus Datos + +```rust +#[derive(Debug, Clone)] +struct Libro { + titulo: String, + autor: String, + paginas: u32, +} + +impl Libro { + fn nuevo(titulo: &str, autor: &str, paginas: u32) -> Libro { + Libro { + titulo: titulo.to_string(), + autor: autor.to_string(), + paginas, + } + } + + fn descripcion(&self) -> String { + format!("'{}' por {} ({} páginas)", self.titulo, self.autor, self.paginas) + } + + // Método que consume y retorna una nueva versión + fn editar_titulo(mut self, nuevo_titulo: &str) -> Libro { + self.titulo = nuevo_titulo.to_string(); + self + } +} + +fn main() { + let libro1 = Libro::nuevo("1984", "George Orwell", 328); + println!("Libro original: {}", libro1.descripcion()); + + // Clonar para mantener el original + let libro2 = libro1.clone().editar_titulo("Animal Farm"); + + println!("Libro original: {}", libro1.descripcion()); + println!("Libro editado: {}", libro2.descripcion()); +} +``` + +## Debug y Display + +### Derivando Debug + +```rust +#[derive(Debug)] +struct Coordenada { + x: f64, + y: f64, +} + +fn main() { + let punto = Coordenada { x: 3.0, y: 4.0 }; + + // Debug formatting + println!("Debug: {:?}", punto); + println!("Pretty debug: {:#?}", punto); +} +``` + +### Implementando Display + +```rust +use std::fmt; + +struct Temperatura { + celsius: f64, +} + +impl fmt::Display for Temperatura { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}°C", self.celsius) + } +} + +impl Temperatura { + fn nueva(celsius: f64) -> Temperatura { + Temperatura { celsius } + } + + fn fahrenheit(&self) -> f64 { + self.celsius * 9.0 / 5.0 + 32.0 + } +} + +fn main() { + let temp = Temperatura::nueva(25.0); + + println!("Temperatura: {}", temp); // Usa Display + println!("En Fahrenheit: {:.1}°F", temp.fahrenheit()); +} +``` + +## Ejercicios Prácticos + +### Ejercicio 1: Sistema de Cuentas Bancarias + +```rust +#[derive(Debug)] +struct CuentaBancaria { + numero: String, + titular: String, + saldo: f64, +} + +impl CuentaBancaria { + fn nueva(numero: String, titular: String) -> CuentaBancaria { + CuentaBancaria { + numero, + titular, + saldo: 0.0, + } + } + + fn depositar(&mut self, cantidad: f64) -> Result<(), String> { + if cantidad <= 0.0 { + return Err("La cantidad debe ser positiva".to_string()); + } + + self.saldo += cantidad; + Ok(()) + } + + fn retirar(&mut self, cantidad: f64) -> Result<(), String> { + if cantidad <= 0.0 { + return Err("La cantidad debe ser positiva".to_string()); + } + + if cantidad > self.saldo { + return Err("Saldo insuficiente".to_string()); + } + + self.saldo -= cantidad; + Ok(()) + } + + fn consultar_saldo(&self) -> f64 { + self.saldo + } + + fn transferir(&mut self, otra_cuenta: &mut CuentaBancaria, cantidad: f64) -> Result<(), String> { + self.retirar(cantidad)?; + otra_cuenta.depositar(cantidad)?; + Ok(()) + } +} + +fn main() { + let mut cuenta1 = CuentaBancaria::nueva( + "12345".to_string(), + "Juan Pérez".to_string() + ); + + let mut cuenta2 = CuentaBancaria::nueva( + "67890".to_string(), + "María García".to_string() + ); + + // Operaciones + cuenta1.depositar(1000.0).unwrap(); + println!("Saldo cuenta1: ${}", cuenta1.consultar_saldo()); + + cuenta1.retirar(200.0).unwrap(); + println!("Después de retirar: ${}", cuenta1.consultar_saldo()); + + // Transferencia + cuenta1.transferir(&mut cuenta2, 300.0).unwrap(); + println!("Cuenta1: ${}", cuenta1.consultar_saldo()); + println!("Cuenta2: ${}", cuenta2.consultar_saldo()); +} +``` + +### Ejercicio 2: Sistema de Biblioteca + +```rust +#[derive(Debug, Clone)] +struct Libro { + id: u32, + titulo: String, + autor: String, + disponible: bool, +} + +#[derive(Debug)] +struct Biblioteca { + libros: Vec, + siguiente_id: u32, +} + +impl Biblioteca { + fn nueva() -> Biblioteca { + Biblioteca { + libros: Vec::new(), + siguiente_id: 1, + } + } + + fn agregar_libro(&mut self, titulo: String, autor: String) { + let libro = Libro { + id: self.siguiente_id, + titulo, + autor, + disponible: true, + }; + + self.libros.push(libro); + self.siguiente_id += 1; + } + + fn buscar_por_titulo(&self, titulo: &str) -> Vec<&Libro> { + self.libros + .iter() + .filter(|libro| libro.titulo.contains(titulo)) + .collect() + } + + fn prestar_libro(&mut self, id: u32) -> Result<(), String> { + if let Some(libro) = self.libros.iter_mut().find(|l| l.id == id) { + if libro.disponible { + libro.disponible = false; + Ok(()) + } else { + Err("El libro ya está prestado".to_string()) + } + } else { + Err("Libro no encontrado".to_string()) + } + } + + fn devolver_libro(&mut self, id: u32) -> Result<(), String> { + if let Some(libro) = self.libros.iter_mut().find(|l| l.id == id) { + if !libro.disponible { + libro.disponible = true; + Ok(()) + } else { + Err("El libro no está prestado".to_string()) + } + } else { + Err("Libro no encontrado".to_string()) + } + } + + fn listar_disponibles(&self) -> Vec<&Libro> { + self.libros + .iter() + .filter(|libro| libro.disponible) + .collect() + } +} + +fn main() { + let mut biblioteca = Biblioteca::nueva(); + + // Agregar libros + biblioteca.agregar_libro("1984".to_string(), "George Orwell".to_string()); + biblioteca.agregar_libro("El Quijote".to_string(), "Cervantes".to_string()); + biblioteca.agregar_libro("Cien años de soledad".to_string(), "García Márquez".to_string()); + + println!("Libros disponibles:"); + for libro in biblioteca.listar_disponibles() { + println!(" {}: '{}' por {}", libro.id, libro.titulo, libro.autor); + } + + // Prestar un libro + biblioteca.prestar_libro(1).unwrap(); + println!("\nDespués de prestar libro ID 1:"); + + for libro in biblioteca.listar_disponibles() { + println!(" {}: '{}' por {}", libro.id, libro.titulo, libro.autor); + } + + // Buscar libros + let resultados = biblioteca.buscar_por_titulo("años"); + println!("\nBuscar 'años':"); + for libro in resultados { + println!(" {}: '{}' por {}", libro.id, libro.titulo, libro.autor); + } +} +``` + +### Ejercicio 3: Juego de Cartas Simple + +```rust +#[derive(Debug, Clone, Copy, PartialEq)] +enum Palo { + Corazones, + Diamantes, + Tréboles, + Espadas, +} + +#[derive(Debug, Clone, Copy)] +enum Valor { + As, + Dos, Tres, Cuatro, Cinco, Seis, Siete, Ocho, Nueve, Diez, + J, Q, K, +} + +#[derive(Debug, Clone, Copy)] +struct Carta { + palo: Palo, + valor: Valor, +} + +#[derive(Debug)] +struct Baraja { + cartas: Vec, +} + +impl Carta { + fn nueva(palo: Palo, valor: Valor) -> Carta { + Carta { palo, valor } + } + + fn valor_numerico(&self) -> u32 { + match self.valor { + Valor::As => 1, + Valor::Dos => 2, + Valor::Tres => 3, + Valor::Cuatro => 4, + Valor::Cinco => 5, + Valor::Seis => 6, + Valor::Siete => 7, + Valor::Ocho => 8, + Valor::Nueve => 9, + Valor::Diez => 10, + Valor::J => 11, + Valor::Q => 12, + Valor::K => 13, + } + } +} + +impl Baraja { + fn nueva() -> Baraja { + let mut cartas = Vec::new(); + + let palos = [Palo::Corazones, Palo::Diamantes, Palo::Tréboles, Palo::Espadas]; + let valores = [ + Valor::As, Valor::Dos, Valor::Tres, Valor::Cuatro, Valor::Cinco, + Valor::Seis, Valor::Siete, Valor::Ocho, Valor::Nueve, Valor::Diez, + Valor::J, Valor::Q, Valor::K, + ]; + + for palo in palos { + for valor in valores { + cartas.push(Carta::nueva(palo, valor)); + } + } + + Baraja { cartas } + } + + fn barajar(&mut self) { + // Implementación simple de mezcla + use std::collections::HashMap; + let mut temp: HashMap = HashMap::new(); + + for (i, carta) in self.cartas.iter().enumerate() { + temp.insert(i, *carta); + } + + // En una implementación real, usarías rand crate + self.cartas.reverse(); // Simplificación para el ejemplo + } + + fn repartir(&mut self) -> Option { + self.cartas.pop() + } + + fn cartas_restantes(&self) -> usize { + self.cartas.len() + } +} + +fn main() { + let mut baraja = Baraja::nueva(); + println!("Baraja creada con {} cartas", baraja.cartas_restantes()); + + baraja.barajar(); + println!("Baraja mezclada"); + + // Repartir algunas cartas + for i in 1..=5 { + if let Some(carta) = baraja.repartir() { + println!("Carta {}: {:?} de {:?} (valor: {})", + i, carta.valor, carta.palo, carta.valor_numerico()); + } + } + + println!("Cartas restantes: {}", baraja.cartas_restantes()); +} +``` + +## Puntos Clave para Recordar + +1. **Las structs agrupan datos relacionados** en un solo tipo +2. **Usa `impl` para definir métodos** y funciones asociadas +3. **`&self` para métodos de lectura**, `&mut self` para modificación, `self` para consumir +4. **Field init shorthand** cuando el nombre del campo coincide con la variable +5. **Struct update syntax** para crear structs basadas en otras +6. **Tuple structs** para tipos simples con nombre +7. **Unit structs** para tipos sin datos (útiles para traits) +8. **Múltiples bloques `impl`** están permitidos +9. **Deriva traits comunes** como `Debug`, `Clone`, `Copy` cuando sea apropiado + +## Próximo Paso + +En el siguiente capítulo exploraremos **Enums** y **Pattern Matching**, que junto con structs forman la base del sistema de tipos de Rust y nos permiten modelar datos de manera más expresiva y segura. \ No newline at end of file diff --git a/src/content/11.enums-pattern-matching.md b/src/content/11.enums-pattern-matching.md new file mode 100644 index 0000000..6bbf105 --- /dev/null +++ b/src/content/11.enums-pattern-matching.md @@ -0,0 +1,765 @@ +--- +nextPath: "gestion-errores" +previousPath: "structs" +editor: true +order: 11 +--- + +# Enums y Pattern Matching + +Los enums (enumeraciones) en Rust son extremadamente poderosos y van mucho más allá de las enumeraciones simples de otros lenguajes. Permiten definir tipos que pueden ser una de varias variantes, y cada variante puede contener diferentes tipos y cantidades de datos. + +## Definiendo Enums + +### Enum Básico + +```rust +#[derive(Debug)] +enum DireccionIP { + V4, + V6, +} + +fn main() { + let casa = DireccionIP::V4; + let oficina = DireccionIP::V6; + + println!("Casa: {:?}", casa); + println!("Oficina: {:?}", oficina); +} +``` + +### Enums con Datos + +```rust +#[derive(Debug)] +enum DireccionIP { + V4(u8, u8, u8, u8), + V6(String), +} + +fn main() { + let casa = DireccionIP::V4(127, 0, 0, 1); + let oficina = DireccionIP::V6(String::from("::1")); + + println!("Casa: {:?}", casa); + println!("Oficina: {:?}", oficina); +} +``` + +### Enums con Diferentes Tipos de Datos + +```rust +#[derive(Debug)] +enum Mensaje { + Salir, + Mover { x: i32, y: i32 }, + Escribir(String), + CambiarColor(i32, i32, i32), +} + +fn main() { + let mensajes = vec![ + Mensaje::Salir, + Mensaje::Mover { x: 10, y: 15 }, + Mensaje::Escribir(String::from("Hola mundo")), + Mensaje::CambiarColor(255, 0, 128), + ]; + + for mensaje in mensajes { + procesar_mensaje(mensaje); + } +} + +fn procesar_mensaje(msg: Mensaje) { + match msg { + Mensaje::Salir => println!("Saliendo del programa"), + Mensaje::Mover { x, y } => println!("Moviendo a ({x}, {y})"), + Mensaje::Escribir(texto) => println!("Escribiendo: {texto}"), + Mensaje::CambiarColor(r, g, b) => println!("Cambiando color a RGB({r}, {g}, {b})"), + } +} +``` + +## Enums con Métodos + +```rust +#[derive(Debug)] +enum Forma { + Rectangulo { ancho: f64, alto: f64 }, + Circulo { radio: f64 }, + Triangulo { base: f64, altura: f64 }, +} + +impl Forma { + fn area(&self) -> f64 { + match self { + Forma::Rectangulo { ancho, alto } => ancho * alto, + Forma::Circulo { radio } => std::f64::consts::PI * radio * radio, + Forma::Triangulo { base, altura } => 0.5 * base * altura, + } + } + + fn perimetro(&self) -> f64 { + match self { + Forma::Rectangulo { ancho, alto } => 2.0 * (ancho + alto), + Forma::Circulo { radio } => 2.0 * std::f64::consts::PI * radio, + Forma::Triangulo { base, altura } => { + // Asumimos triángulo rectángulo para simplificar + let hipotenusa = (base * base + altura * altura).sqrt(); + base + altura + hipotenusa + } + } + } + + fn descripcion(&self) -> String { + match self { + Forma::Rectangulo { ancho, alto } => { + format!("Rectángulo {}x{}", ancho, alto) + } + Forma::Circulo { radio } => { + format!("Círculo con radio {}", radio) + } + Forma::Triangulo { base, altura } => { + format!("Triángulo {}x{}", base, altura) + } + } + } +} + +fn main() { + let formas = vec![ + Forma::Rectangulo { ancho: 10.0, alto: 5.0 }, + Forma::Circulo { radio: 3.0 }, + Forma::Triangulo { base: 4.0, altura: 3.0 }, + ]; + + for forma in &formas { + println!("{}", forma.descripcion()); + println!(" Área: {:.2}", forma.area()); + println!(" Perímetro: {:.2}", forma.perimetro()); + println!(); + } +} +``` + +## El Enum Option + +`Option` es uno de los enums más importantes en Rust, usado para valores que pueden existir o no: + +```rust +fn dividir(a: f64, b: f64) -> Option { + if b != 0.0 { + Some(a / b) + } else { + None + } +} + +fn main() { + let resultados = vec![ + dividir(10.0, 2.0), + dividir(5.0, 0.0), + dividir(8.0, 4.0), + ]; + + for (i, resultado) in resultados.iter().enumerate() { + match resultado { + Some(valor) => println!("Resultado {}: {:.2}", i + 1, valor), + None => println!("Resultado {}: División por cero", i + 1), + } + } + + // Métodos útiles de Option + let numero = Some(5); + + // unwrap: extrae el valor o entra en pánico + println!("Valor: {}", numero.unwrap()); + + // unwrap_or: valor por defecto si es None + let nada: Option = None; + println!("Valor o defecto: {}", nada.unwrap_or(0)); + + // map: transforma el valor si existe + let doble = numero.map(|x| x * 2); + println!("Doble: {:?}", doble); + + // and_then: encadena operaciones que retornan Option + let cuadrado_si_par = numero.and_then(|x| { + if x % 2 == 0 { + Some(x * x) + } else { + None + } + }); + println!("Cuadrado si par: {:?}", cuadrado_si_par); +} +``` + +## El Enum Result + +`Result` se usa para operaciones que pueden fallar: + +```rust +#[derive(Debug)] +enum MiError { + DivisionPorCero, + NumeroNegativo, + Overflow, +} + +fn operacion_segura(a: i32, b: i32) -> Result { + if b == 0 { + return Err(MiError::DivisionPorCero); + } + + if a < 0 || b < 0 { + return Err(MiError::NumeroNegativo); + } + + let resultado = a.checked_mul(b); + match resultado { + Some(valor) => Ok(valor), + None => Err(MiError::Overflow), + } +} + +fn main() { + let operaciones = vec![ + (4, 5), + (10, 0), + (-5, 3), + (1000000, 1000000), + ]; + + for (a, b) in operaciones { + match operacion_segura(a, b) { + Ok(resultado) => println!("{} * {} = {}", a, b, resultado), + Err(error) => println!("{} * {} = Error: {:?}", a, b, error), + } + } + + // Métodos útiles de Result + let resultado = operacion_segura(3, 4); + + // unwrap: extrae el valor o entra en pánico + println!("Resultado: {}", resultado.unwrap()); + + // expect: como unwrap pero con mensaje personalizado + let resultado2 = operacion_segura(2, 6); + println!("Resultado: {}", resultado2.expect("Error en la operación")); + + // unwrap_or: valor por defecto en caso de error + let resultado3 = operacion_segura(5, 0); + println!("Resultado o defecto: {}", resultado3.unwrap_or(-1)); +} +``` + +## Pattern Matching Avanzado + +### Guards en match + +```rust +fn clasificar_numero(num: i32) -> String { + match num { + n if n < 0 => "Negativo".to_string(), + 0 => "Cero".to_string(), + n if n > 0 && n <= 10 => "Pequeño positivo".to_string(), + n if n > 10 && n <= 100 => "Medio".to_string(), + n if n % 2 == 0 => "Grande y par".to_string(), + _ => "Grande e impar".to_string(), + } +} + +fn main() { + let numeros = vec![-5, 0, 3, 15, 50, 123, 100]; + + for num in numeros { + println!("{}: {}", num, clasificar_numero(num)); + } +} +``` + +### Destructuring Complejo + +```rust +#[derive(Debug)] +enum Evento { + Click { x: i32, y: i32, boton: Boton }, + Teclado { tecla: char, modificadores: Vec }, + Scroll { delta: i32 }, +} + +#[derive(Debug)] +enum Boton { + Izquierdo, + Derecho, + Medio, +} + +fn manejar_evento(evento: Evento) { + match evento { + // Destructuring específico + Evento::Click { x: 0, y: 0, boton } => { + println!("Click en origen con botón {:?}", boton); + } + + // Destructuring con guards + Evento::Click { x, y, boton: Boton::Izquierdo } if x > 100 && y > 100 => { + println!("Click izquierdo en área especial ({}, {})", x, y); + } + + // Destructuring general + Evento::Click { x, y, boton } => { + println!("Click {:?} en ({}, {})", boton, x, y); + } + + // Destructuring de Vec + Evento::Teclado { tecla, modificadores } if modificadores.len() > 0 => { + println!("Tecla '{}' con modificadores: {:?}", tecla, modificadores); + } + + Evento::Teclado { tecla, .. } => { + println!("Tecla simple: '{}'", tecla); + } + + Evento::Scroll { delta } => { + let direccion = if delta > 0 { "arriba" } else { "abajo" }; + println!("Scroll {} ({})", direccion, delta.abs()); + } + } +} + +fn main() { + let eventos = vec![ + Evento::Click { x: 0, y: 0, boton: Boton::Izquierdo }, + Evento::Click { x: 150, y: 200, boton: Boton::Izquierdo }, + Evento::Click { x: 50, y: 75, boton: Boton::Derecho }, + Evento::Teclado { + tecla: 'a', + modificadores: vec!["Ctrl".to_string(), "Shift".to_string()] + }, + Evento::Teclado { tecla: 'x', modificadores: vec![] }, + Evento::Scroll { delta: -5 }, + ]; + + for evento in eventos { + manejar_evento(evento); + } +} +``` + +## if let y while let + +Para casos donde solo te interesa un patrón específico: + +```rust +fn main() { + let config_max = Some(3u8); + + // En lugar de match completo + if let Some(max) = config_max { + println!("El máximo configurado es: {}", max); + } + + // Con else + let numero: Option = None; + if let Some(n) = numero { + println!("El número es: {}", n); + } else { + println!("No hay número"); + } + + // while let para procesar hasta que no haya más elementos + let mut pila = vec![1, 2, 3]; + + while let Some(tope) = pila.pop() { + println!("Procesando: {}", tope); + } + + // Procesando Result con if let + let resultado: Result = Ok(42); + + if let Ok(valor) = resultado { + println!("Éxito: {}", valor); + } + + if let Err(error) = resultado { + println!("Error: {}", error); + } +} +``` + +## Ejercicios Prácticos + +### Ejercicio 1: Sistema de Notificaciones + +```rust +#[derive(Debug)] +enum Notificacion { + Email { + destinatario: String, + asunto: String, + cuerpo: String + }, + SMS { + numero: String, + mensaje: String + }, + Push { + titulo: String, + contenido: String, + badge: Option + }, +} + +impl Notificacion { + fn enviar(&self) -> Result<(), String> { + match self { + Notificacion::Email { destinatario, asunto, .. } => { + if destinatario.contains('@') { + println!("📧 Email enviado a {}: {}", destinatario, asunto); + Ok(()) + } else { + Err("Email inválido".to_string()) + } + } + + Notificacion::SMS { numero, mensaje } => { + if numero.len() >= 10 { + println!("📱 SMS enviado a {}: {}", numero, mensaje); + Ok(()) + } else { + Err("Número de teléfono inválido".to_string()) + } + } + + Notificacion::Push { titulo, contenido, badge } => { + let badge_text = match badge { + Some(n) => format!(" [{}]", n), + None => String::new(), + }; + println!("🔔 Push{}: {} - {}", badge_text, titulo, contenido); + Ok(()) + } + } + } + + fn costo(&self) -> f64 { + match self { + Notificacion::Email { .. } => 0.01, + Notificacion::SMS { .. } => 0.05, + Notificacion::Push { .. } => 0.02, + } + } +} + +fn main() { + let notificaciones = vec![ + Notificacion::Email { + destinatario: "usuario@ejemplo.com".to_string(), + asunto: "Bienvenido".to_string(), + cuerpo: "Gracias por registrarte".to_string(), + }, + Notificacion::SMS { + numero: "123456789".to_string(), + mensaje: "Código de verificación: 1234".to_string(), + }, + Notificacion::Push { + titulo: "Nueva actualización".to_string(), + contenido: "Hay funcionalidades nuevas disponibles".to_string(), + badge: Some(3), + }, + ]; + + let mut costo_total = 0.0; + + for notificacion in ¬ificaciones { + match notificacion.enviar() { + Ok(()) => { + costo_total += notificacion.costo(); + println!(" Costo: ${:.2}", notificacion.costo()); + } + Err(error) => { + println!(" ❌ Error: {}", error); + } + } + println!(); + } + + println!("Costo total: ${:.2}", costo_total); +} +``` + +### Ejercicio 2: Calculadora con Manejo de Errores + +```rust +#[derive(Debug)] +enum OperacionMatematica { + Sumar(f64, f64), + Restar(f64, f64), + Multiplicar(f64, f64), + Dividir(f64, f64), + Potencia(f64, f64), + RaizCuadrada(f64), +} + +#[derive(Debug)] +enum ErrorCalculo { + DivisionPorCero, + RaizNegativa, + Overflow, + ResultadoInvalido, +} + +impl OperacionMatematica { + fn calcular(&self) -> Result { + match self { + OperacionMatematica::Sumar(a, b) => Ok(a + b), + OperacionMatematica::Restar(a, b) => Ok(a - b), + OperacionMatematica::Multiplicar(a, b) => { + let resultado = a * b; + if resultado.is_infinite() { + Err(ErrorCalculo::Overflow) + } else { + Ok(resultado) + } + } + OperacionMatematica::Dividir(a, b) => { + if *b == 0.0 { + Err(ErrorCalculo::DivisionPorCero) + } else { + let resultado = a / b; + if resultado.is_nan() || resultado.is_infinite() { + Err(ErrorCalculo::ResultadoInvalido) + } else { + Ok(resultado) + } + } + } + OperacionMatematica::Potencia(base, exponente) => { + let resultado = base.powf(*exponente); + if resultado.is_nan() || resultado.is_infinite() { + Err(ErrorCalculo::ResultadoInvalido) + } else { + Ok(resultado) + } + } + OperacionMatematica::RaizCuadrada(n) => { + if *n < 0.0 { + Err(ErrorCalculo::RaizNegativa) + } else { + Ok(n.sqrt()) + } + } + } + } + + fn descripcion(&self) -> String { + match self { + OperacionMatematica::Sumar(a, b) => format!("{} + {}", a, b), + OperacionMatematica::Restar(a, b) => format!("{} - {}", a, b), + OperacionMatematica::Multiplicar(a, b) => format!("{} * {}", a, b), + OperacionMatematica::Dividir(a, b) => format!("{} / {}", a, b), + OperacionMatematica::Potencia(base, exp) => format!("{}^{}", base, exp), + OperacionMatematica::RaizCuadrada(n) => format!("√{}", n), + } + } +} + +fn main() { + let operaciones = vec![ + OperacionMatematica::Sumar(10.0, 5.0), + OperacionMatematica::Dividir(15.0, 3.0), + OperacionMatematica::Dividir(10.0, 0.0), + OperacionMatematica::RaizCuadrada(16.0), + OperacionMatematica::RaizCuadrada(-4.0), + OperacionMatematica::Potencia(2.0, 10.0), + OperacionMatematica::Multiplicar(f64::MAX, 2.0), + ]; + + for operacion in operaciones { + print!("{} = ", operacion.descripcion()); + + match operacion.calcular() { + Ok(resultado) => println!("{:.2}", resultado), + Err(error) => { + let mensaje = match error { + ErrorCalculo::DivisionPorCero => "Error: División por cero", + ErrorCalculo::RaizNegativa => "Error: Raíz de número negativo", + ErrorCalculo::Overflow => "Error: Desbordamiento numérico", + ErrorCalculo::ResultadoInvalido => "Error: Resultado no válido", + }; + println!("{}", mensaje); + } + } + } +} +``` + +### Ejercicio 3: Sistema de Estados de Juego + +```rust +#[derive(Debug, Clone)] +enum EstadoJuego { + Menu, + Jugando { + nivel: u32, + puntuacion: u32, + vidas: u32 + }, + Pausa { + nivel: u32, + puntuacion: u32, + vidas: u32 + }, + GameOver { + puntuacion_final: u32 + }, + Victoria { + puntuacion_final: u32, + tiempo: u32 + }, +} + +#[derive(Debug)] +enum AccionJuego { + IniciarJuego, + PausarJuego, + ReanudarJuego, + GanarPunto(u32), + PerderVida, + CompletarNivel, + VolverAlMenu, + Reiniciar, +} + +impl EstadoJuego { + fn procesar_accion(&mut self, accion: AccionJuego) -> Result<(), String> { + let nuevo_estado = match (&self, accion) { + // Desde Menu + (EstadoJuego::Menu, AccionJuego::IniciarJuego) => { + EstadoJuego::Jugando { nivel: 1, puntuacion: 0, vidas: 3 } + } + + // Desde Jugando + (EstadoJuego::Jugando { nivel, puntuacion, vidas }, AccionJuego::PausarJuego) => { + EstadoJuego::Pausa { nivel: *nivel, puntuacion: *puntuacion, vidas: *vidas } + } + + (EstadoJuego::Jugando { nivel, puntuacion, vidas }, AccionJuego::GanarPunto(puntos)) => { + EstadoJuego::Jugando { + nivel: *nivel, + puntuacion: puntuacion + puntos, + vidas: *vidas + } + } + + (EstadoJuego::Jugando { nivel, puntuacion, vidas }, AccionJuego::PerderVida) => { + if *vidas <= 1 { + EstadoJuego::GameOver { puntuacion_final: *puntuacion } + } else { + EstadoJuego::Jugando { + nivel: *nivel, + puntuacion: *puntuacion, + vidas: vidas - 1 + } + } + } + + (EstadoJuego::Jugando { nivel, puntuacion, vidas }, AccionJuego::CompletarNivel) => { + if *nivel >= 10 { + EstadoJuego::Victoria { puntuacion_final: *puntuacion, tiempo: 300 } + } else { + EstadoJuego::Jugando { + nivel: nivel + 1, + puntuacion: puntuacion + 100, // Bonus por nivel + vidas: *vidas + } + } + } + + // Desde Pausa + (EstadoJuego::Pausa { nivel, puntuacion, vidas }, AccionJuego::ReanudarJuego) => { + EstadoJuego::Jugando { nivel: *nivel, puntuacion: *puntuacion, vidas: *vidas } + } + + // Acciones globales + (_, AccionJuego::VolverAlMenu) => EstadoJuego::Menu, + (_, AccionJuego::Reiniciar) => EstadoJuego::Menu, + + // Combinaciones inválidas + (estado_actual, accion) => { + return Err(format!("Acción {:?} no válida en estado {:?}", accion, estado_actual)); + } + }; + + *self = nuevo_estado; + Ok(()) + } + + fn descripcion(&self) -> String { + match self { + EstadoJuego::Menu => "En el menú principal".to_string(), + EstadoJuego::Jugando { nivel, puntuacion, vidas } => { + format!("Jugando - Nivel: {}, Puntuación: {}, Vidas: {}", nivel, puntuacion, vidas) + } + EstadoJuego::Pausa { nivel, puntuacion, vidas } => { + format!("En pausa - Nivel: {}, Puntuación: {}, Vidas: {}", nivel, puntuacion, vidas) + } + EstadoJuego::GameOver { puntuacion_final } => { + format!("Game Over - Puntuación final: {}", puntuacion_final) + } + EstadoJuego::Victoria { puntuacion_final, tiempo } => { + format!("¡Victoria! Puntuación: {}, Tiempo: {}s", puntuacion_final, tiempo) + } + } + } +} + +fn main() { + let mut juego = EstadoJuego::Menu; + + let acciones = vec![ + AccionJuego::IniciarJuego, + AccionJuego::GanarPunto(50), + AccionJuego::GanarPunto(25), + AccionJuego::PausarJuego, + AccionJuego::ReanudarJuego, + AccionJuego::PerderVida, + AccionJuego::CompletarNivel, + AccionJuego::GanarPunto(75), + AccionJuego::CompletarNivel, + AccionJuego::VolverAlMenu, + ]; + + println!("Estado inicial: {}", juego.descripcion()); + println!(); + + for (i, accion) in acciones.iter().enumerate() { + println!("Acción {}: {:?}", i + 1, accion); + + match juego.procesar_accion(accion.clone()) { + Ok(()) => println!(" ✅ {}", juego.descripcion()), + Err(error) => println!(" ❌ {}", error), + } + println!(); + } +} +``` + +## Puntos Clave para Recordar + +1. **Los enums pueden contener datos** de diferentes tipos y estructuras +2. **`Option` reemplaza valores nulos** de forma segura +3. **`Result` maneja errores** de forma explícita +4. **`match` debe ser exhaustivo** - cubrir todos los casos posibles +5. **Usa guards para condiciones adicionales** en patterns +6. **`if let` y `while let` simplifican** casos específicos +7. **Los enums pueden tener métodos** como las structs +8. **Destructuring permite extraer datos** de variantes complejas +9. **Combina enums con structs** para modelado de datos poderoso + +## Próximo Paso + +En el siguiente capítulo exploraremos la **Gestión de Errores** en profundidad, incluyendo técnicas avanzadas con `Result`, propagación de errores, y cómo crear sistemas de manejo de errores robustos. \ No newline at end of file diff --git a/src/content/12.temas-avanzados-roadmap.md b/src/content/12.temas-avanzados-roadmap.md new file mode 100644 index 0000000..e7c7854 --- /dev/null +++ b/src/content/12.temas-avanzados-roadmap.md @@ -0,0 +1,681 @@ +--- +nextPath: "" +previousPath: "enums-pattern-matching" +editor: false +order: 12 +--- + +# Temas Avanzados: Completando el Roadmap de Rust + +Este capítulo final cubre los temas restantes del roadmap, proporcionándote una visión general de las características avanzadas de Rust que deberías explorar para convertirte en un desarrollador competente. + +## Gestión de Errores Avanzada + +### Propagación de Errores con ? + +```rust +use std::fs::File; +use std::io::{self, Read}; + +fn leer_archivo(nombre: &str) -> Result { + let mut archivo = File::open(nombre)?; // ? propaga el error automáticamente + let mut contenido = String::new(); + archivo.read_to_string(&mut contenido)?; + Ok(contenido) +} + +// Equivalente sin ?: +fn leer_archivo_verboso(nombre: &str) -> Result { + let mut archivo = match File::open(nombre) { + Ok(archivo) => archivo, + Err(e) => return Err(e), + }; + let mut contenido = String::new(); + match archivo.read_to_string(&mut contenido) { + Ok(_) => Ok(contenido), + Err(e) => Err(e), + } +} +``` + +### Errores Personalizados + +```rust +use std::fmt; +use std::error::Error; + +#[derive(Debug)] +enum MiError { + Io(std::io::Error), + Parse(std::num::ParseIntError), + Custom(String), +} + +impl fmt::Display for MiError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + MiError::Io(err) => write!(f, "Error de IO: {}", err), + MiError::Parse(err) => write!(f, "Error de parseo: {}", err), + MiError::Custom(msg) => write!(f, "Error personalizado: {}", msg), + } + } +} + +impl Error for MiError {} + +// Conversiones automáticas +impl From for MiError { + fn from(err: std::io::Error) -> MiError { + MiError::Io(err) + } +} + +impl From for MiError { + fn from(err: std::num::ParseIntError) -> MiError { + MiError::Parse(err) + } +} +``` + +## Colecciones Esenciales + +### Vector + +```rust +fn main() { + let mut v: Vec = Vec::new(); + v.push(1); + v.push(2); + v.push(3); + + // Acceso seguro + match v.get(2) { + Some(third) => println!("Tercer elemento: {}", third), + None => println!("No hay tercer elemento"), + } + + // Iteración + for i in &v { + println!("{}", i); + } + + // Modificación durante iteración + for i in &mut v { + *i += 50; + } +} +``` + +### HashMap + +```rust +use std::collections::HashMap; + +fn main() { + let mut puntuaciones = HashMap::new(); + puntuaciones.insert(String::from("Azul"), 10); + puntuaciones.insert(String::from("Amarillo"), 50); + + // Actualizar o insertar + let stat = puntuaciones.entry(String::from("Azul")).or_insert(0); + *stat += 10; + + // Iterar + for (clave, valor) in &puntuaciones { + println!("{}: {}", clave, valor); + } +} +``` + +## Módulos y Organización del Código + +### Estructura de Módulos + +```rust +// src/lib.rs +mod network { + fn connect() {} + + mod client { + fn connect() {} + } + + mod server { + fn connect() {} + } +} + +// Usar elementos de módulos +pub use crate::network::client; + +// src/network.rs (archivo separado) +pub mod client { + pub fn connect() { + println!("Conectando cliente..."); + } +} + +pub mod server { + pub fn connect() { + println!("Iniciando servidor..."); + } +} +``` + +### Paths y Use + +```rust +// Diferentes formas de importar +use std::collections::HashMap; +use std::collections::*; +use std::io::{self, Write}; +use std::cmp::Ordering; + +// Re-exports +pub use crate::some_module::SomeType; + +// Alias +use std::collections::HashMap as Map; +``` + +## Traits: Funcionalidad Compartida + +### Definir e Implementar Traits + +```rust +pub trait Resumen { + fn resumir(&self) -> String; + + // Implementación por defecto + fn resumir_autor(&self) -> String { + format!("(Leer más de {}...)", self.resumir()) + } +} + +pub struct Noticia { + pub titular: String, + pub ubicacion: String, + pub autor: String, + pub contenido: String, +} + +impl Resumen for Noticia { + fn resumir(&self) -> String { + format!("{}, por {} ({})", self.titular, self.autor, self.ubicacion) + } +} + +// Traits como parámetros +pub fn notificar(item: &impl Resumen) { + println!("¡Breaking news! {}", item.resumir()); +} + +// Trait bounds +pub fn notificar_detallado(item: &T) { + println!("Detalle: {}", item); +} +``` + +### Traits Derivables + +```rust +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct Persona { + nombre: String, + edad: u32, +} + +fn main() { + let persona1 = Persona { + nombre: "Alice".to_string(), + edad: 30, + }; + + let persona2 = persona1.clone(); // Clone + println!("{:?}", persona1); // Debug + println!("{}", persona1 == persona2); // PartialEq +} +``` + +## Lifetimes: Gestión de Referencias + +### Lifetimes Básicos + +```rust +// Función con lifetime annotation +fn mas_largo<'a>(x: &'a str, y: &'a str) -> &'a str { + if x.len() > y.len() { + x + } else { + y + } +} + +// Struct con lifetimes +struct ImportanteExcerpt<'a> { + parte: &'a str, +} + +impl<'a> ImportanteExcerpt<'a> { + fn nivel(&self) -> i32 { + 3 + } + + fn anunciar_y_retornar_parte(&self, anuncio: &str) -> &str { + println!("¡Atención por favor: {}!", anuncio); + self.parte + } +} +``` + +## Concurrencia + +### Hilos Básicos + +```rust +use std::thread; +use std::time::Duration; + +fn main() { + let handle = thread::spawn(|| { + for i in 1..10 { + println!("hola número {} desde el hilo spawned!", i); + thread::sleep(Duration::from_millis(1)); + } + }); + + for i in 1..5 { + println!("hola número {} desde el hilo principal!", i); + thread::sleep(Duration::from_millis(1)); + } + + handle.join().unwrap(); +} +``` + +### Canales para Comunicación + +```rust +use std::sync::mpsc; +use std::thread; + +fn main() { + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let vals = vec![ + String::from("hola"), + String::from("desde"), + String::from("el"), + String::from("hilo"), + ]; + + for val in vals { + tx.send(val).unwrap(); + } + }); + + for received in rx { + println!("Recibido: {}", received); + } +} +``` + +### Mutex para Estado Compartido + +```rust +use std::sync::{Arc, Mutex}; +use std::thread; + +fn main() { + let counter = Arc::new(Mutex::new(0)); + let mut handles = vec![]; + + for _ in 0..10 { + let counter = Arc::clone(&counter); + let handle = thread::spawn(move || { + let mut num = counter.lock().unwrap(); + *num += 1; + }); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + println!("Resultado: {}", *counter.lock().unwrap()); +} +``` + +## Testing + +### Tests Unitarios + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_suma() { + assert_eq!(2 + 2, 4); + } + + #[test] + fn test_panic() { + panic!("¡Este test fallará!"); + } + + #[test] + #[should_panic(expected = "dividir por cero")] + fn test_division_por_cero() { + divide_por_cero(); + } + + #[test] + fn test_result() -> Result<(), String> { + if 2 + 2 == 4 { + Ok(()) + } else { + Err(String::from("dos más dos no es igual a cuatro")) + } + } + + #[test] + #[ignore] + fn test_costoso() { + // Este test tarda mucho tiempo + } +} + +fn divide_por_cero() { + let _resultado = 10 / 0; +} +``` + +## Ecosistema y Librerías Populares + +### Serialización con Serde + +```toml +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +``` + +```rust +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +struct Persona { + nombre: String, + edad: u8, + telefono: Vec, +} + +fn main() -> serde_json::Result<()> { + let p = Persona { + nombre: "John".to_string(), + edad: 30, + telefono: vec!["555-1234".to_string()], + }; + + let j = serde_json::to_string(&p)?; + println!("JSON: {}", j); + + let p2: Persona = serde_json::from_str(&j)?; + println!("Deserializado: {:?}", p2); + + Ok(()) +} +``` + +### Cliente HTTP con Reqwest + +```toml +[dependencies] +reqwest = { version = "0.11", features = ["json"] } +tokio = { version = "1", features = ["full"] } +``` + +```rust +use reqwest; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +struct Post { + #[serde(rename = "userId")] + user_id: u32, + id: u32, + title: String, + body: String, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let url = "https://jsonplaceholder.typicode.com/posts/1"; + let post: Post = reqwest::get(url) + .await? + .json() + .await?; + + println!("{:#?}", post); + Ok(()) +} +``` + +### CLI con Clap + +```toml +[dependencies] +clap = { version = "4.0", features = ["derive"] } +``` + +```rust +use clap::{Arg, Command, Parser}; + +#[derive(Parser)] +#[command(name = "mi_app")] +#[command(about = "Una aplicación CLI de ejemplo")] +struct Cli { + #[arg(short, long)] + nombre: String, + + #[arg(short, long, default_value_t = 1)] + count: u8, + + #[arg(short, long)] + verbose: bool, +} + +fn main() { + let cli = Cli::parse(); + + for _ in 0..cli.count { + if cli.verbose { + println!("¡Hola {} (modo verboso)!", cli.nombre); + } else { + println!("¡Hola {}!", cli.nombre); + } + } +} +``` + +## Programación Asíncrona + +### Async/Await Básico + +```rust +use tokio::time::{sleep, Duration}; + +async fn decir_hola() { + println!("Hola"); + sleep(Duration::from_secs(1)).await; + println!("Mundo"); +} + +#[tokio::main] +async fn main() { + decir_hola().await; + + // Ejecutar múltiples tareas concurrentemente + let task1 = tokio::spawn(async { + for i in 0..5 { + println!("Tarea 1: {}", i); + sleep(Duration::from_millis(100)).await; + } + }); + + let task2 = tokio::spawn(async { + for i in 0..5 { + println!("Tarea 2: {}", i); + sleep(Duration::from_millis(150)).await; + } + }); + + // Esperar a que ambas tareas terminen + let _ = tokio::join!(task1, task2); +} +``` + +## Macros Básicos + +### Macros Declarativos + +```rust +macro_rules! vec { + ( $( $x:expr ),* ) => { + { + let mut temp_vec = Vec::new(); + $( + temp_vec.push($x); + )* + temp_vec + } + }; +} + +// Macro personalizado +macro_rules! saludar { + ($nombre:expr) => { + println!("¡Hola, {}!", $nombre); + }; + ($nombre:expr, $apellido:expr) => { + println!("¡Hola, {} {}!", $nombre, $apellido); + }; +} + +fn main() { + saludar!("Juan"); + saludar!("María", "García"); +} +``` + +## Unsafe Rust (Conceptos Básicos) + +```rust +fn main() { + let mut num = 5; + + // Crear punteros crudos + let r1 = &num as *const i32; + let r2 = &mut num as *mut i32; + + // Desreferenciar punteros crudos requiere unsafe + unsafe { + println!("r1 apunta a: {}", *r1); + println!("r2 apunta a: {}", *r2); + } + + // Función unsafe + unsafe fn peligroso() {} + + unsafe { + peligroso(); + } +} +``` + +## Próximos Pasos en tu Viaje con Rust + +### Recursos para Continuar Aprendiendo + +1. **Libros:** + - The Rust Programming Language (El Libro oficial) + - Programming Rust (O'Reilly) + - Rust in Action (Manning) + +2. **Práctica:** + - Rustlings: Ejercicios interactivos + - Advent of Code con Rust + - Contribuir a proyectos open source + +3. **Comunidad:** + - Forum oficial: users.rust-lang.org + - Discord: discord.gg/rust-lang + - Reddit: r/rust + - This Week in Rust (newsletter) + +4. **Especialización:** + - **Web Development**: Actix-web, Warp, Rocket + - **GUI**: egui, Tauri, gtk-rs + - **Game Development**: Bevy, ggez + - **Systems Programming**: kernel modules, drivers + - **WebAssembly**: wasm-bindgen, wasm-pack + - **Blockchain**: Substrate, Solana + - **Machine Learning**: Candle, tch + +### Proyectos para Practicar + +1. **Principiante:** + - Calculadora de línea de comandos + - Juego de adivinanzas + - Conversor de unidades + - To-do list simple + +2. **Intermedio:** + - Cliente/servidor HTTP básico + - Base de datos en memoria + - Intérprete de expresiones matemáticas + - Sistema de archivos simple + +3. **Avanzado:** + - Compilador para un lenguaje simple + - Motor de base de datos + - Sistema operativo mínimo + - Blockchain básica + +## Conclusión + +¡Felicidades! Has completado un viaje exhaustivo a través del mundo de Rust. Desde los conceptos fundamentales como ownership y borrowing, hasta temas avanzados como concurrencia y el ecosistema de librerías, ahora tienes las bases sólidas para construir aplicaciones robustas y eficientes. + +### Lo que has aprendido: + +✅ **Fundamentos sólidos**: Sintaxis, tipos de datos, control de flujo +✅ **Conceptos únicos de Rust**: Ownership, borrowing, lifetimes +✅ **Estructuración de código**: Structs, enums, módulos +✅ **Manejo de errores robusto**: Result, Option, propagación +✅ **Colecciones y herramientas**: Vec, HashMap, iteradores +✅ **Funcionalidad compartida**: Traits y genericidad +✅ **Programación concurrente**: Hilos, canales, Mutex +✅ **Testing y calidad**: Tests unitarios, documentación +✅ **Ecosistema rico**: Librerías populares y herramientas + +### El camino por delante: + +Rust es un lenguaje que recompensa la paciencia y el estudio cuidadoso. Sus conceptos únicos pueden ser desafiantes al principio, pero proporcionan una base sólida para escribir código seguro, eficiente y mantenible. + +**Consejos finales:** + +1. **Práctica constante**: La mejor manera de dominar Rust es escribir código +2. **Lee código de otros**: Explora proyectos open source en GitHub +3. **Participa en la comunidad**: Haz preguntas, ayuda a otros +4. **Mantente actualizado**: Rust evoluciona constantemente +5. **Especialízate**: Elige un área que te interese y profundiza + +**¡Ahora es momento de construir cosas increíbles con Rust! 🦀** + +El lenguaje que garantiza seguridad de memoria, ofrece rendimiento de nivel de sistema, y tiene uno de los ecosistemas más amigables del mundo del desarrollo de software te está esperando. + +¡Bienvenido a la comunidad Rust! \ No newline at end of file diff --git a/src/content/2.porque-rust.md b/src/content/2.porque-rust.md index 3d537d6..5582044 100644 --- a/src/content/2.porque-rust.md +++ b/src/content/2.porque-rust.md @@ -1,6 +1,6 @@ --- -nextPath: "versiones" -previousPath: "introduccion" +nextPath: "sintaxis-basica" +previousPath: "configuracion-entorno" editor: false order: 2 --- diff --git a/src/content/3.versiones.md b/src/content/3.versiones.md index 936e1e0..c477b76 100644 --- a/src/content/3.versiones.md +++ b/src/content/3.versiones.md @@ -1,5 +1,5 @@ --- -nextPath: "instalar" +nextPath: "configuracion-entorno" previousPath: "porque-rust" editor: false order: 3 diff --git a/src/content/4.configuracion-entorno.md b/src/content/4.configuracion-entorno.md new file mode 100644 index 0000000..bb3797d --- /dev/null +++ b/src/content/4.configuracion-entorno.md @@ -0,0 +1,345 @@ +--- +nextPath: "sintaxis-basica" +previousPath: "versiones" +editor: true +order: 4 +--- + +# Configurando tu Entorno de Desarrollo Rust + +Antes de comenzar a programar en Rust, necesitas configurar tu entorno de desarrollo correctamente. Esta guía te llevará paso a paso a través del proceso de instalación y configuración. + +## Instalación de Rust + +### Usando rustup (Recomendado) + +`rustup` es la herramienta oficial para instalar y gestionar Rust. Es la forma más fácil y recomendada de instalar Rust. + +**En macOS, Linux y otros sistemas Unix:** + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +**En Windows:** + +1. Ve a https://rustup.rs/ +2. Descarga `rustup-init.exe` +3. Ejecuta el instalador y sigue las instrucciones + +### Verificando la Instalación + +Después de instalar, reinicia tu terminal y verifica que todo esté correctamente instalado: + +```bash +# Verificar que rustc (el compilador) está instalado +rustc --version + +# Verificar que cargo (gestor de paquetes) está instalado +cargo --version + +# Verificar que rustup está instalado +rustup --version +``` + +Deberías ver algo como: + +``` +rustc 1.75.0 (82e1608df 2023-12-21) +cargo 1.75.0 (1d8b05cdd 2023-11-20) +rustup 1.26.0 (5af9b9484 2023-04-05) +``` + +## Herramientas Esenciales + +Rust viene con varias herramientas integradas que te ayudarán en el desarrollo: + +### rustc - El Compilador + +`rustc` es el compilador de Rust. Aunque normalmente usarás `cargo`, es útil conocer cómo compilar archivos individuales: + +```bash +# Compilar un archivo simple +rustc main.rs + +# Compilar con optimizaciones +rustc -O main.rs + +# Ver opciones de compilación +rustc --help +``` + +### cargo - Gestor de Paquetes y Herramienta de Construcción + +`cargo` es el sistema de construcción y gestor de paquetes de Rust: + +```bash +# Crear un nuevo proyecto +cargo new mi_proyecto +cd mi_proyecto + +# Crear un nuevo proyecto como librería +cargo new mi_libreria --lib + +# Construir el proyecto +cargo build + +# Construir con optimizaciones (release) +cargo build --release + +# Ejecutar el proyecto +cargo run + +# Ejecutar tests +cargo test + +# Verificar que el código compila sin construir +cargo check + +# Actualizar dependencias +cargo update +``` + +### rustup - Gestor de Versiones + +`rustup` te permite gestionar múltiples versiones de Rust: + +```bash +# Ver versiones instaladas +rustup toolchain list + +# Instalar una versión específica +rustup toolchain install 1.70.0 + +# Cambiar versión por defecto +rustup default stable + +# Actualizar Rust +rustup update + +# Ver información del sistema +rustup show +``` + +### rustfmt - Formateador de Código + +`rustfmt` formatea automáticamente tu código Rust según el estilo oficial: + +```bash +# Formatear un archivo +rustfmt src/main.rs + +# Formatear todo el proyecto +cargo fmt + +# Verificar formato sin cambiar archivos +cargo fmt -- --check +``` + +### clippy - Linter para Mejores Prácticas + +`clippy` es un linter que te ayuda a escribir mejor código Rust: + +```bash +# Instalar clippy (viene por defecto con rustup) +rustup component add clippy + +# Ejecutar clippy en tu proyecto +cargo clippy + +# Clippy con correcciones automáticas +cargo clippy --fix +``` + +## Configuración del Editor + +### Visual Studio Code + +VS Code es una excelente opción para Rust: + +1. **Instala la extensión rust-analyzer:** + - Ve a extensiones (Ctrl+Shift+X) + - Busca "rust-analyzer" + - Instala la extensión oficial + +2. **Extensiones útiles adicionales:** + - "CodeLLDB" para debugging + - "Better TOML" para archivos Cargo.toml + - "Error Lens" para mostrar errores inline + +### Configuración recomendada para VS Code + +Crea `.vscode/settings.json` en tu proyecto: + +```json +{ + "rust-analyzer.check.command": "clippy", + "rust-analyzer.cargo.features": "all", + "editor.formatOnSave": true, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer" + } +} +``` + +### Otros Editores Populares + +- **IntelliJ IDEA/CLion**: Plugin IntelliJ Rust +- **Vim/Neovim**: rust.vim + coc-rust-analyzer +- **Emacs**: rust-mode + lsp-mode +- **Sublime Text**: LSP-rust-analyzer + +## Tu Primer Proyecto + +Vamos a crear tu primer proyecto Rust para probar que todo funciona: + +```bash +# Crear nuevo proyecto +cargo new hola_mundo +cd hola_mundo +``` + +Esto crea la siguiente estructura: + +``` +hola_mundo/ +├── Cargo.toml +└── src/ + └── main.rs +``` + +**Cargo.toml** (manifiesto del proyecto): + +```toml +[package] +name = "hola_mundo" +version = "0.1.0" +edition = "2021" + +[dependencies] +``` + +**src/main.rs** (código fuente): + +```rust +fn main() { + println!("¡Hola, mundo!"); +} +``` + +### Ejecutar el Proyecto + +```bash +# Compilar y ejecutar +cargo run +``` + +Deberías ver: + +``` + Compiling hola_mundo v0.1.0 (/path/to/hola_mundo) + Finished dev [unoptimized + debuginfo] target(s) in 0.50s + Running `target/debug/hola_mundo` +¡Hola, mundo! +``` + +## Configuración Avanzada + +### Targets de Compilación + +Rust puede compilar para múltiples plataformas: + +```bash +# Ver targets disponibles +rustup target list + +# Instalar target específico +rustup target add x86_64-pc-windows-gnu + +# Compilar para target específico +cargo build --target x86_64-pc-windows-gnu +``` + +### Variables de Entorno Útiles + +```bash +# Ubicación de instalación de Rust +export RUSTUP_HOME="$HOME/.rustup" +export CARGO_HOME="$HOME/.cargo" + +# Agregar cargo bin al PATH +export PATH="$CARGO_HOME/bin:$PATH" + +# Configurar nivel de log para cargo +export CARGO_LOG=info + +# Habilitar backtrace completo en panic +export RUST_BACKTRACE=1 +``` + +### Configuración de Cargo + +Puedes configurar cargo globalmente en `~/.cargo/config.toml`: + +```toml +[build] +# Usar linker más rápido en macOS +rustflags = ["-C", "link-arg=-fuse-ld=lld"] + +[cargo-new] +# Template por defecto para nuevos proyectos +vcs = "git" + +[registries] +# Configurar registries alternativos +``` + +## Solución de Problemas Comunes + +### Error: "linker not found" + +**En Ubuntu/Debian:** +```bash +sudo apt install build-essential +``` + +**En macOS:** +```bash +xcode-select --install +``` + +### Problemas con SSL/TLS + +```bash +# Actualizar certificados +rustup self update +``` + +### Espacio en Disco + +```bash +# Limpiar archivos de compilación +cargo clean + +# Limpiar cache de cargo +cargo install cargo-cache +cargo cache --autoclean +``` + +## Próximos Pasos + +Con tu entorno configurado, estás listo para: + +1. **Aprender la sintaxis básica** de Rust +2. **Escribir tu primer programa** real +3. **Explorar el ecosistema** de crates (librerías) +4. **Contribuir a proyectos** open source + +## Recursos Adicionales + +- **Documentación oficial:** https://doc.rust-lang.org/ +- **Rust Playground:** https://play.rust-lang.org/ (para probar código online) +- **Rustlings:** Ejercicios interactivos para aprender +- **Rust by Example:** https://doc.rust-lang.org/rust-by-example/ + +¡Felicidades! Ahora tienes un entorno de desarrollo Rust completamente funcional. En el próximo capítulo, aprenderemos la sintaxis básica del lenguaje. \ No newline at end of file diff --git a/src/content/5.sintaxis-basica.md b/src/content/5.sintaxis-basica.md new file mode 100644 index 0000000..e841837 --- /dev/null +++ b/src/content/5.sintaxis-basica.md @@ -0,0 +1,380 @@ +--- +nextPath: "tipos-datos" +previousPath: "configuracion-entorno" +editor: true +order: 5 +--- + +# Sintaxis Básica de Rust + +En este capítulo exploraremos los fundamentos de la sintaxis de Rust. Aprenderás sobre variables, funciones, comentarios y las convenciones básicas del lenguaje. + +## Tu Primer Programa + +```rust +fn main() { + println!("¡Hola, mundo!"); +} +``` + +Este simple programa ilustra varios conceptos importantes: +- `fn` define una función +- `main` es el punto de entrada del programa +- `println!` es un macro (nota el `!`) +- Los bloques de código van entre llaves `{}` + +## Variables y Mutabilidad + +En Rust, las variables son **inmutables por defecto**. Esta es una característica de seguridad fundamental. + +### Variables Inmutables + +```rust +fn main() { + let x = 5; + println!("El valor de x es: {x}"); + + // x = 6; // ¡Esto causaría un error de compilación! +} +``` + +### Variables Mutables + +Para hacer una variable mutable, usa la palabra clave `mut`: + +```rust +fn main() { + let mut x = 5; + println!("El valor de x es: {x}"); + + x = 6; // ¡Ahora esto es válido! + println!("El valor de x es: {x}"); +} +``` + +### Constantes + +Las constantes son siempre inmutables y deben tener un tipo explícito: + +```rust +const TRES_HORAS_EN_SEGUNDOS: u32 = 60 * 60 * 3; + +fn main() { + println!("Tres horas son {} segundos", TRES_HORAS_EN_SEGUNDOS); +} +``` + +**Diferencias entre constantes y variables inmutables:** +- Las constantes se evalúan en tiempo de compilación +- Se pueden declarar en cualquier scope, incluyendo el global +- Solo pueden asignarse a expresiones constantes +- Por convención, se nombran en MAYÚSCULAS con guiones bajos + +### Shadowing (Sombreado) + +Puedes declarar una nueva variable con el mismo nombre que una anterior: + +```rust +fn main() { + let x = 5; + + let x = x + 1; // Nueva variable x que "sombrea" la anterior + + { + let x = x * 2; // Otra nueva variable x en este scope + println!("El valor de x en el scope interno es: {x}"); // 12 + } + + println!("El valor de x es: {x}"); // 6 +} +``` + +**Ventajas del shadowing:** +- Puedes cambiar el tipo de la variable +- Mantiene la inmutabilidad por defecto +- Evita tener que inventar nombres como `x_str`, `x_num` + +```rust +fn main() { + let espacios = " "; // String + let espacios = espacios.len(); // usize - ¡diferente tipo! + + println!("Número de espacios: {espacios}"); +} +``` + +## Funciones + +Las funciones son bloques de código reutilizables. En Rust, el estilo es usar `snake_case` para nombres de funciones. + +### Función Básica + +```rust +fn main() { + println!("¡Hola, mundo!"); + otra_funcion(); +} + +fn otra_funcion() { + println!("Esta es otra función."); +} +``` + +### Funciones con Parámetros + +```rust +fn main() { + saludar("Alice", 25); + saludar("Bob", 30); +} + +fn saludar(nombre: &str, edad: u32) { + println!("¡Hola {nombre}! Tienes {edad} años."); +} +``` + +**Importante:** En Rust, debes especificar el tipo de cada parámetro. + +### Funciones que Retornan Valores + +```rust +fn main() { + let resultado = sumar(5, 3); + println!("5 + 3 = {resultado}"); + + let (suma, producto) = sumar_y_multiplicar(4, 7); + println!("4 + 7 = {suma}, 4 * 7 = {producto}"); +} + +fn sumar(a: i32, b: i32) -> i32 { + a + b // Expresión de retorno (sin punto y coma) +} + +fn sumar_y_multiplicar(a: i32, b: i32) -> (i32, i32) { + (a + b, a * b) // Retornando una tupla +} +``` + +**Conceptos importantes:** +- `->` indica el tipo de retorno +- Las expresiones no llevan punto y coma +- Las declaraciones terminan con punto y coma +- La última expresión se retorna automáticamente +- También puedes usar `return` explícitamente + +### Statements vs Expressions + +```rust +fn main() { + // Statement (declaración) - no retorna un valor + let x = 5; + + // Expression (expresión) - retorna un valor + let y = { + let x = 3; + x + 1 // Sin punto y coma - es una expresión + }; + + println!("El valor de y es: {y}"); // 4 + + // Esto sería un error porque las declaraciones no retornan valores: + // let x = (let y = 6); // ERROR! +} +``` + +## Comentarios + +### Comentarios de Línea + +```rust +fn main() { + // Este es un comentario de línea + println!("¡Hola!"); // También puedes comentar al final de la línea +} +``` + +### Comentarios de Documentación + +```rust +/// Esta función suma dos números. +/// +/// # Ejemplos +/// +/// ``` +/// let resultado = sumar(2, 3); +/// assert_eq!(resultado, 5); +/// ``` +fn sumar(a: i32, b: i32) -> i32 { + a + b +} + +/// Esta estructura representa una persona. +/// +/// # Campos +/// +/// * `nombre` - El nombre de la persona +/// * `edad` - La edad de la persona +struct Persona { + nombre: String, + edad: u32, +} +``` + +Los comentarios `///` generan documentación HTML con `cargo doc`. + +## Macros Básicos + +### println! - Imprimir en Consola + +```rust +fn main() { + // Impresión básica + println!("¡Hola, mundo!"); + + // Con variables + let nombre = "Alice"; + let edad = 30; + println!("Mi nombre es {nombre} y tengo {edad} años"); + + // Con posiciones + println!("Me llamo {0} y tengo {1} años. Sí, {0} es mi nombre.", nombre, edad); + + // Con nombres + println!("Me llamo {nombre} y tengo {edad} años", nombre="Bob", edad=25); + + // Con formato + println!("Pi es aproximadamente {:.2}", 3.14159); // 3.14 + println!("En hexadecimal: {:x}", 255); // ff + println!("En binario: {:b}", 8); // 1000 +} +``` + +### print! vs println! + +```rust +fn main() { + print!("Este texto "); + print!("está en "); + println!("la misma línea"); + + println!("Esta es una nueva línea"); +} +``` + +### eprintln! - Imprimir Errores + +```rust +fn main() { + println!("Esto va a stdout"); + eprintln!("Esto va a stderr"); +} +``` + +## Convenciones de Nombres + +Rust tiene convenciones específicas para nombres: + +### snake_case +- **Variables**: `mi_variable` +- **Funciones**: `mi_funcion` +- **Módulos**: `mi_modulo` + +### SCREAMING_SNAKE_CASE +- **Constantes**: `MI_CONSTANTE` +- **Valores estáticos**: `MI_VALOR_ESTATICO` + +### PascalCase +- **Tipos (structs, enums)**: `MiStruct` +- **Traits**: `MiTrait` + +```rust +// Ejemplos de convenciones +const PI: f64 = 3.14159; +static MAX_USUARIOS: u32 = 1000; + +struct Usuario { + nombre_completo: String, + edad: u32, +} + +fn crear_usuario(nombre: String, edad: u32) -> Usuario { + Usuario { + nombre_completo: nombre, + edad, + } +} + +fn main() { + let nuevo_usuario = crear_usuario(String::from("Alice"), 30); + println!("Usuario creado: {}", nuevo_usuario.nombre_completo); +} +``` + +## Ejercicios Prácticos + +### Ejercicio 1: Calculadora Básica + +```rust +fn main() { + let a = 10; + let b = 3; + + println!("{a} + {b} = {}", sumar(a, b)); + println!("{a} - {b} = {}", restar(a, b)); + println!("{a} * {b} = {}", multiplicar(a, b)); + println!("{a} / {b} = {}", dividir(a, b)); +} + +fn sumar(a: i32, b: i32) -> i32 { + a + b +} + +fn restar(a: i32, b: i32) -> i32 { + a - b +} + +fn multiplicar(a: i32, b: i32) -> i32 { + a * b +} + +fn dividir(a: i32, b: i32) -> f64 { + a as f64 / b as f64 +} +``` + +### Ejercicio 2: Temperatura + +```rust +fn main() { + let celsius = 25.0; + let fahrenheit = celsius_a_fahrenheit(celsius); + + println!("{celsius}°C = {fahrenheit:.1}°F"); + + let fahrenheit = 77.0; + let celsius = fahrenheit_a_celsius(fahrenheit); + + println!("{fahrenheit}°F = {celsius:.1}°C"); +} + +fn celsius_a_fahrenheit(celsius: f64) -> f64 { + celsius * 9.0 / 5.0 + 32.0 +} + +fn fahrenheit_a_celsius(fahrenheit: f64) -> f64 { + (fahrenheit - 32.0) * 5.0 / 9.0 +} +``` + +## Puntos Clave para Recordar + +1. **Las variables son inmutables por defecto** - usa `mut` cuando necesites mutabilidad +2. **Las funciones requieren tipos explícitos** para parámetros y valores de retorno +3. **Las expresiones no llevan punto y coma** al final si quieres que retornen un valor +4. **Usa snake_case para variables y funciones**, PascalCase para tipos +5. **Los comentarios `///` generan documentación** +6. **`println!` es un macro**, no una función (nota el `!`) + +## Próximo Paso + +Ahora que conoces la sintaxis básica, en el siguiente capítulo exploraremos los tipos de datos de Rust en detalle, incluyendo enteros, flotantes, booleanos, caracteres, tuplas y arrays. \ No newline at end of file diff --git a/src/content/6.tipos-datos.md b/src/content/6.tipos-datos.md new file mode 100644 index 0000000..1cd3f61 --- /dev/null +++ b/src/content/6.tipos-datos.md @@ -0,0 +1,514 @@ +--- +nextPath: "estructuras-control" +previousPath: "sintaxis-basica" +editor: true +order: 6 +--- + +# Tipos de Datos en Rust + +Rust es un lenguaje de tipado estático, lo que significa que debe conocer los tipos de todas las variables en tiempo de compilación. En este capítulo exploraremos los tipos de datos disponibles en Rust. + +## Tipos Escalares + +Los tipos escalares representan un solo valor. Rust tiene cuatro tipos escalares primarios. + +### Enteros + +Los enteros son números sin parte fraccionaria. Rust proporciona varios tamaños: + +| Longitud | Con signo | Sin signo | +|----------|-----------|-----------| +| 8-bit | i8 | u8 | +| 16-bit | i16 | u16 | +| 32-bit | i32 | u32 | +| 64-bit | i64 | u64 | +| 128-bit | i128 | u128 | +| arch | isize | usize | + +```rust +fn main() { + // Enteros con signo (pueden ser negativos) + let edad: u8 = 30; // 0 a 255 + let temperatura: i32 = -10; // -2^31 a 2^31-1 + let poblacion: u64 = 8_000_000; // Usa guiones bajos para legibilidad + + // El tipo por defecto es i32 + let numero = 42; // i32 + + // Enteros dependientes de la arquitectura + let indice: usize = 0; // Usado para indexación, tamaño del puntero + let offset: isize = -5; // Con signo, tamaño del puntero + + // Diferentes bases numéricas + let decimal = 98_222; // Base 10 + let hexadecimal = 0xff; // Base 16 + let octal = 0o77; // Base 8 + let binario = 0b1111_0000; // Base 2 + let byte = b'A'; // Solo u8 + + println!("Decimal: {decimal}"); + println!("Hexadecimal: {hexadecimal}"); + println!("Octal: {octal}"); + println!("Binario: {binario}"); + println!("Byte: {byte}"); +} +``` + +### Overflow de Enteros + +```rust +fn main() { + // En modo debug, esto causaría panic + // En modo release, haría "wrapping" + let mut x: u8 = 255; + // x = x + 1; // Overflow! + + // Maneras seguras de manejar overflow: + + // 1. Wrapping: da la vuelta + x = x.wrapping_add(1); + println!("Wrapping: {x}"); // 0 + + // 2. Checked: retorna Option + let y: u8 = 255; + match y.checked_add(1) { + Some(valor) => println!("Resultado: {valor}"), + None => println!("¡Overflow detectado!"), + } + + // 3. Overflowing: retorna tupla (resultado, overflow_ocurrió) + let (resultado, overflow) = y.overflowing_add(1); + println!("Resultado: {resultado}, Overflow: {overflow}"); + + // 4. Saturating: se queda en el límite + let z = y.saturating_add(1); + println!("Saturating: {z}"); // 255 +} +``` + +### Números de Punto Flotante + +Rust tiene dos tipos de punto flotante: `f32` y `f64`. El tipo por defecto es `f64`. + +```rust +fn main() { + let x = 2.0; // f64 por defecto + let y: f32 = 3.0; // f32 explícito + + // Operaciones con punto flotante + let pi: f64 = 3.14159265359; + let e: f32 = 2.718281828; + + println!("Pi: {pi}"); + println!("E: {e}"); + + // Valores especiales + let infinito = f64::INFINITY; + let neg_infinito = f64::NEG_INFINITY; + let nan = f64::NAN; + + println!("Infinito: {infinito}"); + println!("Infinito negativo: {neg_infinito}"); + println!("NaN: {nan}"); + + // Verificar valores especiales + println!("¿Es infinito? {}", infinito.is_infinite()); + println!("¿Es NaN? {}", nan.is_nan()); + println!("¿Es finito? {}", pi.is_finite()); +} +``` + +### Operaciones Aritméticas + +```rust +fn main() { + // Operaciones básicas + let suma = 5 + 10; + let diferencia = 95.5 - 4.3; + let producto = 4 * 30; + let cociente = 56.7 / 32.2; + let residuo = 43 % 5; + + println!("Suma: {suma}"); + println!("Diferencia: {diferencia}"); + println!("Producto: {producto}"); + println!("Cociente: {cociente}"); + println!("Residuo: {residuo}"); + + // Operaciones con asignación + let mut x = 5; + x += 3; // x = x + 3 + x -= 1; // x = x - 1 + x *= 2; // x = x * 2 + x /= 4; // x = x / 4 + x %= 3; // x = x % 3 + + println!("Resultado final: {x}"); +} +``` + +### Tipo Boolean + +El tipo `bool` puede ser `true` o `false`: + +```rust +fn main() { + let t = true; + let f: bool = false; // Con anotación de tipo explícita + + // Operaciones lógicas + let and = t && f; // false + let or = t || f; // true + let not = !t; // false + + println!("AND: {and}"); + println!("OR: {or}"); + println!("NOT: {not}"); + + // Los booleanos ocupan 1 byte + println!("Tamaño de bool: {} bytes", std::mem::size_of::()); +} +``` + +### Tipo Character + +El tipo `char` representa un valor escalar Unicode: + +```rust +fn main() { + let c = 'z'; + let z: char = 'ℤ'; + let corazon = '♥'; + let emoji = '😻'; + + println!("Caracteres: {c}, {z}, {corazon}, {emoji}"); + + // Los chars ocupan 4 bytes (UTF-32) + println!("Tamaño de char: {} bytes", std::mem::size_of::()); + + // Convertir entre char y u32 + let numero: u32 = 'A' as u32; + let caracter: char = 65 as u8 as char; + + println!("'A' como número: {numero}"); + println!("65 como carácter: {caracter}"); +} +``` + +## Tipos Compuestos + +Los tipos compuestos agrupan múltiples valores en un solo tipo. + +### Tuplas + +Las tuplas agrupan valores de diferentes tipos: + +```rust +fn main() { + // Crear una tupla + let tupla: (i32, f64, char) = (500, 6.4, 'R'); + + // Destructuring (desestructuración) + let (x, y, z) = tupla; + println!("x: {x}, y: {y}, z: {z}"); + + // Acceso por índice + let quinientos = tupla.0; + let seis_coma_cuatro = tupla.1; + let r = tupla.2; + + println!("Primer elemento: {quinientos}"); + println!("Segundo elemento: {seis_coma_cuatro}"); + println!("Tercer elemento: {r}"); + + // Tupla vacía (unit type) + let unit = (); + println!("Unit type: {:?}", unit); +} +``` + +### Funciones que Retornan Tuplas + +```rust +fn main() { + let (suma, producto) = calcular(4, 5); + println!("4 + 5 = {suma}"); + println!("4 * 5 = {producto}"); + + // Ignorar valores con _ + let (_, area) = dimensiones_rectangulo(3.0, 4.0); + println!("Área: {area}"); +} + +fn calcular(a: i32, b: i32) -> (i32, i32) { + (a + b, a * b) +} + +fn dimensiones_rectangulo(ancho: f64, alto: f64) -> (f64, f64) { + (ancho * 2.0 + alto * 2.0, ancho * alto) +} +``` + +### Arrays + +Los arrays tienen un tamaño fijo y todos los elementos deben ser del mismo tipo: + +```rust +fn main() { + // Declaración de array + let numeros: [i32; 5] = [1, 2, 3, 4, 5]; + + // Shortcut para valores repetidos + let cincos = [5; 3]; // [5, 5, 5] + + // Acceso a elementos + let primero = numeros[0]; + let segundo = numeros[1]; + + println!("Primero: {primero}"); + println!("Segundo: {segundo}"); + + // Longitud del array + println!("Longitud: {}", numeros.len()); + + // Iterando sobre un array + for elemento in numeros { + println!("Elemento: {elemento}"); + } + + // Con índices + for (i, elemento) in numeros.iter().enumerate() { + println!("Índice {i}: {elemento}"); + } +} +``` + +### Acceso Seguro a Arrays + +```rust +fn main() { + let array = [1, 2, 3, 4, 5]; + + // Acceso directo (puede causar panic) + // let elemento = array[10]; // ¡Panic en runtime! + + // Acceso seguro con get() + match array.get(10) { + Some(valor) => println!("Elemento en índice 10: {valor}"), + None => println!("Índice fuera de rango"), + } + + // Usando if let + if let Some(valor) = array.get(2) { + println!("Elemento en índice 2: {valor}"); + } +} +``` + +### Slices + +Los slices son referencias a una porción de un array: + +```rust +fn main() { + let array = [1, 2, 3, 4, 5, 6]; + + // Slice completo + let slice_completo = &array; + + // Slice parcial + let slice = &array[1..4]; // [2, 3, 4] + let desde_inicio = &array[..3]; // [1, 2, 3] + let hasta_final = &array[2..]; // [3, 4, 5, 6] + + println!("Slice completo: {:?}", slice_completo); + println!("Slice parcial: {:?}", slice); + println!("Desde inicio: {:?}", desde_inicio); + println!("Hasta final: {:?}", hasta_final); + + // Longitud de slices + println!("Longitud del slice: {}", slice.len()); +} +``` + +## Strings + +En Rust hay dos tipos principales de strings: `&str` y `String`. + +### String Slices (&str) + +```rust +fn main() { + // String literal (tipo &str) + let saludo = "¡Hola, mundo!"; + + // Slice de String + let s = String::from("¡Hola, mundo!"); + let slice = &s[0..5]; // "¡Hola" + + println!("String literal: {saludo}"); + println!("String slice: {slice}"); + + // Los string literals son inmutables + // saludo.push_str("!!!"); // ERROR: no se puede modificar +} +``` + +### String (Heap-allocated) + +```rust +fn main() { + // Crear un String mutable + let mut s = String::new(); + s.push_str("¡Hola"); + s.push(' '); + s.push_str("mundo!"); + + println!("String construido: {s}"); + + // Desde string literal + let s2 = String::from("¡Hola, mundo!"); + let s3 = "¡Hola, mundo!".to_string(); + + println!("s2: {s2}"); + println!("s3: {s3}"); + + // Operaciones con String + let mut texto = String::from("Rust"); + texto.push_str(" es"); + texto.push(' '); + texto.push_str("genial"); + + println!("Texto final: {texto}"); + println!("Longitud: {}", texto.len()); + println!("¿Está vacío? {}", texto.is_empty()); +} +``` + +## Conversiones Entre Tipos + +### Casting Explícito + +```rust +fn main() { + // Casting entre enteros + let a: u8 = 10; + let b: u16 = a as u16; + let c: i32 = b as i32; + + println!("a: {a}, b: {b}, c: {c}"); + + // Casting puede truncar valores + let grande: u32 = 300; + let pequeño: u8 = grande as u8; // 300 % 256 = 44 + + println!("Grande: {grande}, Pequeño: {pequeño}"); + + // Punto flotante a entero (trunca decimales) + let f = 3.7; + let entero = f as i32; // 3 + + println!("Float: {f}, Entero: {entero}"); +} +``` + +### Parsing desde Strings + +```rust +fn main() { + // Parse exitoso + let numero_str = "42"; + let numero: i32 = numero_str.parse().unwrap(); + println!("Número parseado: {numero}"); + + // Parse con manejo de errores + let texto = "123.45"; + match texto.parse::() { + Ok(num) => println!("Número: {num}"), + Err(e) => println!("Error parseando: {e}"), + } + + // Parse que falla + let malo = "no_es_numero"; + match malo.parse::() { + Ok(num) => println!("Número: {num}"), + Err(_) => println!("No se pudo parsear '{malo}'"), + } +} +``` + +## Ejercicios Prácticos + +### Ejercicio 1: Calculadora de IMC + +```rust +fn main() { + let peso: f64 = 70.5; // kg + let altura: f64 = 1.75; // metros + + let imc = calcular_imc(peso, altura); + let categoria = clasificar_imc(imc); + + println!("Peso: {peso} kg"); + println!("Altura: {altura} m"); + println!("IMC: {:.2}", imc); + println!("Clasificación: {categoria}"); +} + +fn calcular_imc(peso: f64, altura: f64) -> f64 { + peso / (altura * altura) +} + +fn clasificar_imc(imc: f64) -> &'static str { + if imc < 18.5 { + "Bajo peso" + } else if imc < 25.0 { + "Peso normal" + } else if imc < 30.0 { + "Sobrepeso" + } else { + "Obesidad" + } +} +``` + +### Ejercicio 2: Análisis de Array + +```rust +fn main() { + let numeros = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]; + + let (suma, promedio, minimo, maximo) = analizar_array(&numeros); + + println!("Array: {:?}", numeros); + println!("Suma: {suma}"); + println!("Promedio: {:.2}", promedio); + println!("Mínimo: {minimo}"); + println!("Máximo: {maximo}"); +} + +fn analizar_array(arr: &[i32]) -> (i32, f64, i32, i32) { + let suma: i32 = arr.iter().sum(); + let promedio = suma as f64 / arr.len() as f64; + let minimo = *arr.iter().min().unwrap(); + let maximo = *arr.iter().max().unwrap(); + + (suma, promedio, minimo, maximo) +} +``` + +## Puntos Clave para Recordar + +1. **Rust es de tipado estático** - todos los tipos deben conocerse en tiempo de compilación +2. **i32 es el tipo entero por defecto**, f64 para punto flotante +3. **Los arrays tienen tamaño fijo**, los slices son referencias flexibles +4. **char en Rust es Unicode** y ocupa 4 bytes +5. **Las tuplas pueden contener tipos diferentes** +6. **&str es inmutable**, String es mutable y está en el heap +7. **Usa `as` para casting explícito** entre tipos primitivos +8. **`parse()` convierte strings a otros tipos** + +## Próximo Paso + +En el siguiente capítulo aprenderemos sobre las estructuras de control en Rust: condicionales, bucles y pattern matching, que nos permitirán tomar decisiones y repetir código de manera eficiente. \ No newline at end of file diff --git a/src/content/7.estructuras-control.md b/src/content/7.estructuras-control.md new file mode 100644 index 0000000..9893c14 --- /dev/null +++ b/src/content/7.estructuras-control.md @@ -0,0 +1,575 @@ +--- +nextPath: "ownership" +previousPath: "tipos-datos" +editor: true +order: 7 +--- + +# Estructuras de Control en Rust + +Las estructuras de control nos permiten dirigir el flujo de ejecución de nuestros programas. En este capítulo exploraremos condicionales, bucles y pattern matching en Rust. + +## Expresiones Condicionales + +### if Básico + +```rust +fn main() { + let numero = 6; + + if numero % 4 == 0 { + println!("El número es divisible por 4"); + } else if numero % 3 == 0 { + println!("El número es divisible por 3"); + } else if numero % 2 == 0 { + println!("El número es divisible por 2"); + } else { + println!("El número no es divisible por 4, 3, o 2"); + } +} +``` + +### if como Expresión + +En Rust, `if` es una expresión, no solo una declaración: + +```rust +fn main() { + let condicion = true; + + // if como expresión + let numero = if condicion { 5 } else { 6 }; + println!("El valor de numero es: {numero}"); + + // Clasificar edad + let edad = 25; + let categoria = if edad < 13 { + "niño" + } else if edad < 20 { + "adolescente" + } else if edad < 65 { + "adulto" + } else { + "adulto mayor" + }; + + println!("Categoría: {categoria}"); + + // Los tipos deben coincidir + // let error = if condicion { 5 } else { "seis" }; // ERROR! +} +``` + +### Condiciones Complejas + +```rust +fn main() { + let temperatura = 22; + let llueve = false; + let es_fin_de_semana = true; + + // Operadores lógicos + if temperatura > 20 && !llueve { + println!("¡Perfecto para salir!"); + } + + if es_fin_de_semana || temperatura > 25 { + println!("Buen día para relajarse"); + } + + // Expresiones más complejas + let actividad = if temperatura > 30 { + if llueve { + "quedarse en casa con aire acondicionado" + } else { + "ir a la playa" + } + } else if temperatura < 10 { + "quedarse en casa" + } else if llueve { + "ir al cine" + } else { + "dar un paseo" + }; + + println!("Actividad recomendada: {actividad}"); +} +``` + +## Bucles + +Rust proporciona tres tipos de bucles: `loop`, `while`, y `for`. + +### loop - Bucle Infinito + +```rust +fn main() { + let mut contador = 0; + + // Bucle infinito con break + loop { + contador += 1; + + if contador == 3 { + continue; // Salta a la siguiente iteración + } + + println!("Contador: {contador}"); + + if contador == 5 { + break; // Sale del bucle + } + } + + println!("Fin del bucle"); +} +``` + +### Retornar Valores desde loop + +```rust +fn main() { + let mut contador = 0; + + // loop puede retornar un valor + let resultado = loop { + contador += 1; + + if contador == 10 { + break contador * 2; // Retorna el valor + } + }; + + println!("El resultado es: {resultado}"); // 20 +} +``` + +### Etiquetas de Bucle + +```rust +fn main() { + let mut cuenta = 0; + + // Etiquetas para bucles anidados + 'externo: loop { + println!("Bucle externo: {cuenta}"); + let mut restante = 10; + + loop { + println!(" Bucle interno: {restante}"); + if restante == 9 { + break; // Sale solo del bucle interno + } + if cuenta == 2 { + break 'externo; // Sale del bucle externo + } + restante -= 1; + } + + cuenta += 1; + } + + println!("Fin de bucles anidados"); +} +``` + +### while - Bucle Condicional + +```rust +fn main() { + let mut numero = 3; + + // Bucle while básico + while numero != 0 { + println!("{numero}!"); + numero -= 1; + } + println!("¡DESPEGUE!"); + + // while con condiciones complejas + let mut x = 1; + while x < 100 { + x *= 2; + println!("x = {x}"); + } + + // Cuidado con bucles infinitos + let mut bandera = true; + let mut iteraciones = 0; + + while bandera { + iteraciones += 1; + println!("Iteración: {iteraciones}"); + + if iteraciones >= 5 { + bandera = false; // Evita bucle infinito + } + } +} +``` + +### for - Iteración sobre Colecciones + +```rust +fn main() { + // Iterar sobre array + let array = [10, 20, 30, 40, 50]; + + for elemento in array { + println!("Elemento: {elemento}"); + } + + // Iterar con índices + for (indice, valor) in array.iter().enumerate() { + println!("Índice {indice}: {valor}"); + } + + // Rangos + for numero in 1..4 { // Excluye el 4 + println!("Número: {numero}"); // 1, 2, 3 + } + + for numero in 1..=4 { // Incluye el 4 + println!("Número inclusivo: {numero}"); // 1, 2, 3, 4 + } + + // Rango inverso + for numero in (1..4).rev() { + println!("Número reverso: {numero}"); // 3, 2, 1 + } +} +``` + +### Patrones de Iteración Comunes + +```rust +fn main() { + let datos = vec![1, 2, 3, 4, 5]; + + // 1. Iterar por valor (consume la colección) + for item in datos.clone() { + println!("Por valor: {item}"); + } + + // 2. Iterar por referencia inmutable + for item in &datos { + println!("Por referencia: {item}"); + } + + // 3. Iterar por referencia mutable + let mut datos_mut = vec![1, 2, 3, 4, 5]; + for item in &mut datos_mut { + *item *= 2; // Modificar cada elemento + } + println!("Datos modificados: {:?}", datos_mut); + + // 4. Usar collect para crear nueva colección + let cuadrados: Vec = (1..6) + .map(|x| x * x) + .collect(); + println!("Cuadrados: {:?}", cuadrados); +} +``` + +## Pattern Matching con match + +`match` es una característica poderosa de Rust para pattern matching: + +### match Básico + +```rust +fn main() { + let numero = 3; + + match numero { + 1 => println!("Uno"), + 2 => println!("Dos"), + 3 => println!("Tres"), + 4 => println!("Cuatro"), + 5 => println!("Cinco"), + _ => println!("Otro número"), // Comodín (catch-all) + } +} +``` + +### match como Expresión + +```rust +fn main() { + let numero = 6; + + let descripcion = match numero { + 1 => "uno", + 2 => "dos", + 3 => "tres", + n if n > 10 => "número grande", + _ => "número normal", + }; + + println!("El número {numero} es: {descripcion}"); +} +``` + +### Patrones Múltiples + +```rust +fn main() { + let valor = 4; + + match valor { + 1 | 2 => println!("Uno o dos"), + 3..=5 => println!("Entre tres y cinco"), + 6 | 7 | 8 => println!("Seis, siete u ocho"), + _ => println!("Otro valor"), + } +} +``` + +### Destructuring con match + +```rust +fn main() { + // Con tuplas + let punto = (3, 5); + + match punto { + (0, 0) => println!("Origen"), + (0, y) => println!("En el eje Y: {y}"), + (x, 0) => println!("En el eje X: {x}"), + (x, y) => println!("Punto ({x}, {y})"), + } + + // Con arrays + let array = [1, 2, 3]; + + match array { + [a, 2, c] => println!("Segundo elemento es 2: [{a}, 2, {c}]"), + [a, b, c] => println!("Array: [{a}, {b}, {c}]"), + } +} +``` + +### Guards en match + +```rust +fn main() { + let numero = Some(4); + + match numero { + Some(x) if x < 5 => println!("Menor que cinco: {x}"), + Some(x) => println!("Mayor o igual a cinco: {x}"), + None => println!("No hay valor"), + } + + // Ejemplo más complejo + let par_x_y = (4, -2); + + match par_x_y { + (x, y) if x == y => println!("Estos son iguales"), + (x, y) if x + y == 0 => println!("Se suman cero"), + (x, _) if x % 2 == 0 => println!("X es par"), + _ => println!("Sin patrón especial"), + } +} +``` + +## if let - Sintaxis Concisa + +Para casos donde solo te interesa un patrón específico: + +```rust +fn main() { + let config_max = Some(3u8); + + // Con match + match config_max { + Some(max) => println!("El máximo está configurado a {max}"), + _ => (), + } + + // Con if let (más conciso) + if let Some(max) = config_max { + println!("El máximo está configurado a {max}"); + } + + // Combinado con else + let numero = Some(7); + + if let Some(n) = numero { + if n > 5 { + println!("Número grande: {n}"); + } + } else { + println!("No hay número"); + } +} +``` + +## while let - Bucle Condicional con Pattern Matching + +```rust +fn main() { + let mut pila = Vec::new(); + + pila.push(1); + pila.push(2); + pila.push(3); + + // Procesar hasta que la pila esté vacía + while let Some(tope) = pila.pop() { + println!("Procesando: {tope}"); + } + + println!("Pila vacía"); +} +``` + +## Ejercicios Prácticos + +### Ejercicio 1: Juego de Adivinanza + +```rust +fn main() { + let numero_secreto = 7; + let mut intentos = 0; + let max_intentos = 5; + + loop { + intentos += 1; + + // Simular adivinanza (normalmente leerías input del usuario) + let adivinanza = match intentos { + 1 => 3, + 2 => 9, + 3 => 7, + _ => 5, + }; + + println!("Intento {intentos}: {adivinanza}"); + + match adivinanza { + n if n == numero_secreto => { + println!("¡Ganaste en {intentos} intentos!"); + break; + }, + n if n < numero_secreto => println!("Muy bajo"), + n if n > numero_secreto => println!("Muy alto"), + _ => unreachable!(), + } + + if intentos >= max_intentos { + println!("Se agotaron los intentos. El número era {numero_secreto}"); + break; + } + } +} +``` + +### Ejercicio 2: Clasificador de Caracteres + +```rust +fn main() { + let texto = "Hola Rust 123!"; + + let mut letras = 0; + let mut numeros = 0; + let mut espacios = 0; + let mut otros = 0; + + for caracter in texto.chars() { + match caracter { + 'a'..='z' | 'A'..='Z' => letras += 1, + '0'..='9' => numeros += 1, + ' ' => espacios += 1, + _ => otros += 1, + } + } + + println!("Análisis de '{texto}':"); + println!("Letras: {letras}"); + println!("Números: {numeros}"); + println!("Espacios: {espacios}"); + println!("Otros: {otros}"); +} +``` + +### Ejercicio 3: Tabla de Multiplicar + +```rust +fn main() { + let numero = 7; + let limite = 10; + + println!("Tabla de multiplicar del {numero}:"); + println!("------------------------"); + + for i in 1..=limite { + let resultado = numero * i; + + // Formatting con match + let descripcion = match i { + 1 => "primera", + 2 => "segunda", + 3 => "tercera", + n if n <= 10 => "normal", + _ => "alta", + }; + + println!("{numero} x {i:2} = {resultado:3} ({descripcion} fila)"); + } +} +``` + +### Ejercicio 4: Contador de Palabras + +```rust +fn main() { + let frase = "Rust es un lenguaje de programación moderno y seguro"; + let palabras: Vec<&str> = frase.split_whitespace().collect(); + + println!("Frase: '{frase}'"); + println!("Palabras encontradas: {}", palabras.len()); + println!(); + + for (indice, palabra) in palabras.iter().enumerate() { + let categoria = match palabra.len() { + 1..=3 => "corta", + 4..=6 => "media", + 7..=10 => "larga", + _ => "muy larga", + }; + + println!("Palabra {}: '{}' - {} caracteres ({})", + indice + 1, palabra, palabra.len(), categoria); + } + + // Estadísticas + let palabra_mas_larga = palabras.iter() + .max_by_key(|palabra| palabra.len()) + .unwrap(); + + let palabra_mas_corta = palabras.iter() + .min_by_key(|palabra| palabra.len()) + .unwrap(); + + println!("\nEstadísticas:"); + println!("Palabra más larga: '{palabra_mas_larga}' ({} chars)", palabra_mas_larga.len()); + println!("Palabra más corta: '{palabra_mas_corta}' ({} chars)", palabra_mas_corta.len()); +} +``` + +## Puntos Clave para Recordar + +1. **`if` es una expresión** - puede retornar valores +2. **`loop` crea bucles infinitos** - usa `break` para salir +3. **`while` ejecuta mientras la condición sea verdadera** +4. **`for` es ideal para iterar** sobre colecciones y rangos +5. **`match` debe ser exhaustivo** - cubrir todos los casos posibles +6. **Usa `_` como comodín** para casos no manejados explícitamente +7. **`if let` y `while let`** son útiles para casos específicos +8. **Los guards (`if`)** añaden condiciones adicionales a los patrones +9. **Las etiquetas de bucle** ayudan con bucles anidados + +## Próximo Paso + +En el siguiente capítulo exploraremos uno de los conceptos más importantes y únicos de Rust: el **Ownership** (sistema de propiedad). Este sistema es lo que permite a Rust garantizar seguridad de memoria sin un recolector de basura. \ No newline at end of file diff --git a/src/content/8.ownership.md b/src/content/8.ownership.md new file mode 100644 index 0000000..e3e62f1 --- /dev/null +++ b/src/content/8.ownership.md @@ -0,0 +1,474 @@ +--- +nextPath: "borrowing" +previousPath: "estructuras-control" +editor: true +order: 8 +--- + +# Ownership: El Corazón de Rust + +El sistema de ownership (propiedad) es la característica más distintiva de Rust y lo que permite garantizar seguridad de memoria sin un recolector de basura. Entender ownership es fundamental para programar efectivamente en Rust. + +## ¿Qué es Ownership? + +Ownership es un sistema que gestiona la memoria automáticamente siguiendo un conjunto de reglas que el compilador verifica en tiempo de compilación. Si cualquiera de estas reglas se viola, el programa no compilará. + +### Las Tres Reglas de Ownership + +1. **Cada valor en Rust tiene un único propietario** +2. **Solo puede haber un propietario a la vez** +3. **Cuando el propietario sale del scope, el valor se elimina** + +## Scope (Ámbito) + +Un scope es el rango dentro del programa donde una variable es válida: + +```rust +fn main() { + // s no está disponible aquí, no ha sido declarada aún + + { + let s = "hola"; // s es válida desde este punto + + // hacer cosas con s + println!("{s}"); + } // este scope termina, s ya no es válida + + // println!("{s}"); // ERROR: s no está en scope +} +``` + +## Tipos de Datos y Memoria + +### Stack vs Heap + +```rust +fn main() { + // Datos en el stack (tamaño conocido, fijo) + let x = 5; // i32 en stack + let y = x; // se copia el valor + + println!("x: {x}, y: {y}"); // Ambos son válidos + + // Datos en el heap (tamaño variable) + let s1 = String::from("hola"); // String en heap + let s2 = s1; // se mueve la propiedad + + // println!("{s1}"); // ERROR: s1 ya no es válido + println!("{s2}"); // Solo s2 es válido +} +``` + +### Tipos Copy vs Move + +```rust +fn main() { + // Tipos que implementan Copy (se duplican automáticamente) + let x = 5; + let y = x; // x se copia + println!("x: {x}, y: {y}"); // Ambos funcionan + + // Tipos que no implementan Copy (se mueven) + let s1 = String::from("mundo"); + let s2 = s1; // s1 se mueve a s2 + // println!("{s1}"); // ERROR: value borrowed here after move + + // Para duplicar, usa clone() + let s3 = String::from("rust"); + let s4 = s3.clone(); // Copia profunda + println!("s3: {s3}, s4: {s4}"); // Ambos funcionan +} +``` + +### Tipos que Implementan Copy + +```rust +fn main() { + // Tipos escalares + let a = 5; // i32 + let b = true; // bool + let c = 'R'; // char + let d = 3.14; // f64 + + // Tuplas de tipos Copy + let punto = (3, 4); // (i32, i32) + let otro_punto = punto; // Se copia + + println!("Original: {:?}", punto); + println!("Copia: {:?}", otro_punto); + + // Arrays de tipos Copy + let array = [1, 2, 3, 4, 5]; + let otro_array = array; // Se copia + + println!("Original: {:?}", array); + println!("Copia: {:?}", otro_array); +} +``` + +## Ownership y Funciones + +### Pasar Valores a Funciones + +```rust +fn main() { + let s = String::from("hola"); // s entra en scope + + tomar_ownership(s); // s se mueve a la función + // s ya no es válido aquí + + // println!("{s}"); // ERROR: value borrowed here after move + + let x = 5; // x entra en scope + + hacer_copia(x); // x se copia a la función + // x sigue siendo válido + println!("x sigue siendo: {x}"); // Esto funciona + +} // x sale de scope, pero s ya había salido cuando se movió + +fn tomar_ownership(texto: String) { // texto entra en scope + println!("{texto}"); +} // texto sale de scope y se elimina + +fn hacer_copia(entero: i32) { // entero entra en scope + println!("{entero}"); +} // entero sale de scope, pero no pasa nada especial +``` + +### Retornar Valores desde Funciones + +```rust +fn main() { + let s1 = dar_ownership(); // función mueve su valor de retorno a s1 + + let s2 = String::from("hola"); // s2 entra en scope + + let s3 = tomar_y_dar_ownership(s2); // s2 se mueve a la función, + // que mueve su valor de retorno a s3 + + println!("s1: {s1}"); + // println!("{s2}"); // ERROR: s2 ya no es válido + println!("s3: {s3}"); + +} // s3 sale de scope y se elimina + // s1 sale de scope y se elimina + // s2 ya había salido de scope, no pasa nada + +fn dar_ownership() -> String { // función mueve su valor de retorno + let texto = String::from("tuyo"); // texto entra en scope + texto // texto se retorna y sale de scope +} + +fn tomar_y_dar_ownership(texto: String) -> String { // texto entra en scope + texto // texto se retorna y sale de scope +} +``` + +## Patrones Comunes con Ownership + +### Retornar Múltiples Valores + +```rust +fn main() { + let s1 = String::from("hola"); + + let (s2, longitud) = calcular_longitud(s1); + + println!("La longitud de '{s2}' es {longitud}."); +} + +fn calcular_longitud(s: String) -> (String, usize) { + let longitud = s.len(); // len() retorna la longitud de un String + + (s, longitud) // Retornamos tanto el String como su longitud +} +``` + +### Clonar para Mantener Ownership + +```rust +fn main() { + let s1 = String::from("hola mundo"); + + // Clonar antes de pasar a la función + let longitud = calcular_longitud_clonando(s1.clone()); + + println!("'{s1}' tiene {longitud} caracteres"); + + // También podemos usar to_owned() + let palabras = contar_palabras(s1.to_owned()); + println!("'{s1}' tiene {palabras} palabras"); +} + +fn calcular_longitud_clonando(s: String) -> usize { + s.len() +} // s sale de scope y se elimina (es una copia) + +fn contar_palabras(s: String) -> usize { + s.split_whitespace().count() +} // s sale de scope y se elimina +``` + +## Ownership con Colecciones + +### Vectores y Ownership + +```rust +fn main() { + let mut v = vec![1, 2, 3, 4, 5]; + + // Tomar ownership del vector + let v2 = v; // v se mueve a v2 + // println!("{:?}", v); // ERROR: value borrowed here after move + + // Crear nuevo vector + let mut numeros = vec![10, 20, 30]; + + // Procesar sin mover + procesar_vector(numeros.clone()); + println!("Original aún disponible: {:?}", numeros); + + // Mover definitivamente + let resultado = tomar_vector(numeros); + println!("Resultado: {:?}", resultado); + // println!("{:?}", numeros); // ERROR: moved +} + +fn procesar_vector(v: Vec) { + println!("Procesando: {:?}", v); +} // v se elimina aquí + +fn tomar_vector(mut v: Vec) -> Vec { + v.push(40); + v +} // Retorna el vector modificado +``` + +### HashMap y Ownership + +```rust +use std::collections::HashMap; + +fn main() { + let mut scores = HashMap::new(); + + // Tipos Copy se insertan sin problema + scores.insert(String::from("Azul"), 10); + scores.insert(String::from("Amarillo"), 50); + + // Los String se mueven al HashMap + let field_name = String::from("Favorito"); + let field_value = String::from("Azul"); + + let mut map = HashMap::new(); + map.insert(field_name, field_value); + // field_name y field_value ya no son válidos + + // Para mantener ownership, clona antes de insertar + let key = String::from("nuevo"); + let value = String::from("valor"); + + map.insert(key.clone(), value.clone()); + println!("Key: {key}, Value: {value}"); // Aún disponibles + + println!("Mapa: {:?}", map); +} +``` + +## Drop y RAII + +El trait `Drop` permite personalizar qué ocurre cuando un valor sale de scope: + +```rust +struct CustomSmartPointer { + data: String, +} + +impl Drop for CustomSmartPointer { + fn drop(&mut self) { + println!("Eliminando CustomSmartPointer con data '{}'!", self.data); + } +} + +fn main() { + let c = CustomSmartPointer { + data: String::from("mi dato"), + }; + + let d = CustomSmartPointer { + data: String::from("otro dato"), + }; + + println!("CustomSmartPointers creados."); + + // Eliminar manualmente con drop() + drop(c); // Equivale a std::mem::drop(c) + println!("CustomSmartPointer eliminado antes del final de scope"); + +} // d se elimina automáticamente aquí +``` + +## Ejercicios Prácticos + +### Ejercicio 1: Gestión de Strings + +```rust +fn main() { + // Crear un string + let mensaje = crear_mensaje("Hola".to_string(), "Rust".to_string()); + println!("Mensaje: {mensaje}"); + + // Procesar sin perder ownership + let (procesado, longitud) = procesar_mensaje(mensaje); + println!("Procesado: {procesado} (longitud: {longitud})"); + + // Usar el string procesado + mostrar_mensaje(procesado); +} + +fn crear_mensaje(saludo: String, nombre: String) -> String { + format!("{saludo}, {nombre}!") +} + +fn procesar_mensaje(mut msg: String) -> (String, usize) { + msg.push_str(" 🦀"); + let len = msg.len(); + (msg, len) +} + +fn mostrar_mensaje(msg: String) { + println!("Mostrando: {msg}"); +} // msg se elimina aquí +``` + +### Ejercicio 2: Lista de Tareas + +```rust +fn main() { + let mut tareas = vec![ + String::from("Aprender Rust"), + String::from("Hacer ejercicios"), + String::from("Construir un proyecto"), + ]; + + println!("Tareas originales:"); + mostrar_tareas(tareas.clone()); // Clonar para mantener ownership + + // Agregar nueva tarea + tareas = agregar_tarea(tareas, String::from("Compartir conocimiento")); + + println!("\nDespués de agregar:"); + mostrar_tareas(tareas.clone()); + + // Completar primera tarea + let (tareas_restantes, completada) = completar_primera_tarea(tareas); + println!("\nTarea completada: {completada}"); + + println!("Tareas restantes:"); + mostrar_tareas(tareas_restantes); +} + +fn mostrar_tareas(tareas: Vec) { + for (i, tarea) in tareas.iter().enumerate() { + println!(" {}. {}", i + 1, tarea); + } +} + +fn agregar_tarea(mut tareas: Vec, nueva_tarea: String) -> Vec { + tareas.push(nueva_tarea); + tareas +} + +fn completar_primera_tarea(mut tareas: Vec) -> (Vec, String) { + let completada = tareas.remove(0); + (tareas, completada) +} +``` + +### Ejercicio 3: Contador con Ownership + +```rust +fn main() { + let contador = crear_contador(0); + println!("Contador inicial: {contador}"); + + let contador = incrementar(contador, 5); + println!("Después de incrementar 5: {contador}"); + + let contador = decrementar(contador, 2); + println!("Después de decrementar 2: {contador}"); + + let (contador, es_par) = verificar_paridad(contador); + println!("Contador: {contador}, ¿Es par? {es_par}"); +} + +fn crear_contador(inicial: i32) -> i32 { + inicial +} + +fn incrementar(contador: i32, cantidad: i32) -> i32 { + contador + cantidad +} + +fn decrementar(contador: i32, cantidad: i32) -> i32 { + contador - cantidad +} + +fn verificar_paridad(contador: i32) -> (i32, bool) { + (contador, contador % 2 == 0) +} +``` + +## Errores Comunes con Ownership + +### Error 1: Usar después de Mover + +```rust +fn main() { + let s1 = String::from("hola"); + let s2 = s1; // s1 se mueve a s2 + + // println!("{s1}"); // ERROR: borrow of moved value + + // Solución: clonar + let s3 = String::from("mundo"); + let s4 = s3.clone(); + println!("{s3} {s4}"); // Ambos funcionan +} +``` + +### Error 2: Retornar Referencias Locales + +```rust +// ¡ESTO NO COMPILA! +// fn crear_string() -> &String { +// let s = String::from("hola"); +// &s // ERROR: returns a reference to data owned by the current function +// } + +// Solución: retornar el valor, no una referencia +fn crear_string() -> String { + String::from("hola") // Transfiere ownership +} + +fn main() { + let s = crear_string(); + println!("{s}"); +} +``` + +## Puntos Clave para Recordar + +1. **Cada valor tiene un único propietario** en cualquier momento +2. **Mover transfiere ownership**, el valor original ya no es accesible +3. **Clonar crea una copia independiente** pero es más costoso +4. **Los tipos Copy se duplican automáticamente** (enteros, bool, char, etc.) +5. **Las funciones pueden tomar y retornar ownership** +6. **Usar `drop()` para eliminar valores manualmente** +7. **RAII: los recursos se liberan automáticamente** al salir de scope +8. **El compilador previene use-after-free** y double-free automáticamente + +## Próximo Paso + +El ownership puede ser restrictivo cuando solo quieres "prestar" un valor temporalmente. En el siguiente capítulo aprenderemos sobre **Borrowing** y **Referencias**, que nos permiten usar valores sin tomar su ownership. \ No newline at end of file diff --git a/src/content/9.borrowing.md b/src/content/9.borrowing.md new file mode 100644 index 0000000..47e7ee9 --- /dev/null +++ b/src/content/9.borrowing.md @@ -0,0 +1,622 @@ +--- +nextPath: "structs" +previousPath: "ownership" +editor: true +order: 9 +--- + +# Borrowing: Referencias Seguras en Rust + +El borrowing (préstamo) permite usar valores sin tomar su ownership. Las referencias nos permiten referir a un valor sin ser responsables de liberarlo, resolviendo muchos problemas que surgen con el sistema de ownership. + +## ¿Qué es Borrowing? + +Borrowing es crear referencias a un valor sin tomar su ownership. Es como "pedir prestado" el valor - puedes usarlo, pero debes devolverlo intacto. + +```rust +fn main() { + let s1 = String::from("hola"); + + // Crear una referencia (borrowing) + let longitud = calcular_longitud(&s1); + + // s1 sigue siendo válido porque no transferimos ownership + println!("La longitud de '{s1}' es {longitud}"); +} + +fn calcular_longitud(s: &String) -> usize { + s.len() +} // s sale de scope, pero no elimina el valor porque no es propietario +``` + +## Referencias Inmutables + +Por defecto, las referencias son inmutables: + +```rust +fn main() { + let s = String::from("hola mundo"); + + // Múltiples referencias inmutables están permitidas + let r1 = &s; + let r2 = &s; + let r3 = &s; + + println!("r1: {r1}"); + println!("r2: {r2}"); + println!("r3: {r3}"); + + // Todas las referencias pueden coexistir + println!("Original: {s}"); + + // Pasar referencias a funciones + imprimir_string(&s); + analizar_string(r1); +} + +fn imprimir_string(texto: &String) { + println!("Imprimiendo: {texto}"); +} + +fn analizar_string(texto: &String) { + println!("Longitud: {}", texto.len()); + println!("¿Está vacío? {}", texto.is_empty()); + // texto.push_str("!!!"); // ERROR: no se puede modificar +} +``` + +## Referencias Mutables + +Para modificar un valor a través de una referencia, necesitas una referencia mutable: + +```rust +fn main() { + let mut s = String::from("hola"); + + // Crear referencia mutable + cambiar_string(&mut s); + + println!("Después del cambio: {s}"); +} + +fn cambiar_string(texto: &mut String) { + texto.push_str(", mundo!"); +} +``` + +### Restricciones de Referencias Mutables + +Solo puede haber **una** referencia mutable a un valor en un scope dado: + +```rust +fn main() { + let mut s = String::from("hola"); + + let r1 = &mut s; + // let r2 = &mut s; // ERROR: cannot borrow `s` as mutable more than once + + println!("{r1}"); + + // r1 ya no se usa después de esto, así que podemos crear otra + let r2 = &mut s; + println!("{r2}"); +} +``` + +## Reglas de Borrowing + +### La Regla Fundamental + +**No puedes tener referencias mutables e inmutables al mismo tiempo:** + +```rust +fn main() { + let mut s = String::from("hola"); + + let r1 = &s; // OK - referencia inmutable + let r2 = &s; // OK - otra referencia inmutable + // let r3 = &mut s; // ERROR: cannot borrow as mutable while borrowed as immutable + + println!("{r1} y {r2}"); + // r1 y r2 ya no se usan después de este punto + + let r3 = &mut s; // OK - ahora podemos tener una referencia mutable + println!("{r3}"); +} +``` + +### Lifetime de Referencias + +Las referencias tienen un "lifetime" - deben ser válidas mientras se usan: + +```rust +fn main() { + let mut s = String::from("hola"); + + { + let r = &s; // OK - r vive hasta el final de este bloque + println!("{r}"); + } // r sale de scope aquí + + // Ahora podemos crear una referencia mutable + let r_mut = &mut s; + r_mut.push_str(" mundo"); + println!("{r_mut}"); +} +``` + +## Tipos de Referencias + +### Referencias a Diferentes Tipos + +```rust +fn main() { + // Referencias a tipos primitivos + let numero = 42; + let ref_numero = № + println!("Número: {numero}, Referencia: {ref_numero}"); + + // Referencias a arrays + let array = [1, 2, 3, 4, 5]; + let ref_array = &array; + println!("Primer elemento: {}", ref_array[0]); + + // Referencias a tuplas + let tupla = (10, "hola"); + let ref_tupla = &tupla; + println!("Segundo elemento: {}", ref_tupla.1); +} +``` + +### Referencias vs Punteros + +```rust +fn main() { + let x = 5; + let ref_x = &x; + + // Dereferencing con * + println!("Valor de x: {x}"); + println!("Valor a través de referencia: {}", *ref_x); + println!("Referencia (automática): {ref_x}"); // Rust desreferencia automáticamente + + // Con strings + let s = String::from("hola"); + let ref_s = &s; + + println!("Longitud directa: {}", s.len()); + println!("Longitud por referencia: {}", ref_s.len()); // Desreferenciado automático + println!("Longitud explícita: {}", (*ref_s).len()); // Desreferenciado manual +} +``` + +## Slices: Referencias a Porciones + +Los slices son referencias a una porción contigua de una colección: + +### String Slices + +```rust +fn main() { + let s = String::from("hola mundo"); + + // Crear slices + let hola = &s[0..4]; // "hola" + let mundo = &s[5..10]; // "mundo" + let completo = &s[..]; // "hola mundo" + let desde_inicio = &s[..5]; // "hola " + let hasta_final = &s[5..]; // "mundo" + + println!("Original: {s}"); + println!("Hola: {hola}"); + println!("Mundo: {mundo}"); + println!("Completo: {completo}"); + + // Función que retorna un slice + let primera_palabra = obtener_primera_palabra(&s); + println!("Primera palabra: {primera_palabra}"); +} + +fn obtener_primera_palabra(s: &String) -> &str { + let bytes = s.as_bytes(); + + for (i, &item) in bytes.iter().enumerate() { + if item == b' ' { + return &s[0..i]; + } + } + + &s[..] // Si no hay espacios, retorna toda la string +} +``` + +### Mejorando con &str + +```rust +fn main() { + let s = String::from("hola mundo"); + + // Esta función funciona con String y &str + let palabra1 = primera_palabra(&s); + let palabra2 = primera_palabra("hola rust"); + + println!("Palabra 1: {palabra1}"); + println!("Palabra 2: {palabra2}"); +} + +// Mejor: usar &str en lugar de &String +fn primera_palabra(s: &str) -> &str { + let bytes = s.as_bytes(); + + for (i, &item) in bytes.iter().enumerate() { + if item == b' ' { + return &s[0..i]; + } + } + + s +} +``` + +### Array Slices + +```rust +fn main() { + let array = [1, 2, 3, 4, 5]; + + // Diferentes slices del array + let slice_completo = &array; + let slice_parcial = &array[1..4]; // [2, 3, 4] + let primeros_tres = &array[..3]; // [1, 2, 3] + + println!("Array completo: {:?}", slice_completo); + println!("Slice parcial: {:?}", slice_parcial); + println!("Primeros tres: {:?}", primeros_tres); + + // Pasar slices a funciones + imprimir_slice(slice_parcial); + let suma = sumar_slice(&array[2..]); + println!("Suma desde índice 2: {suma}"); +} + +fn imprimir_slice(slice: &[i32]) { + println!("Imprimiendo slice: {:?}", slice); +} + +fn sumar_slice(slice: &[i32]) -> i32 { + slice.iter().sum() +} +``` + +## Patrones Comunes con Borrowing + +### Funciones que Solo Leen + +```rust +fn main() { + let texto = String::from("Rust es genial"); + let numeros = vec![1, 2, 3, 4, 5]; + + // Funciones que solo leen no necesitan ownership + mostrar_info(&texto); + estadisticas(&numeros); + + // Los valores originales siguen disponibles + println!("Texto original: {texto}"); + println!("Números originales: {:?}", numeros); +} + +fn mostrar_info(s: &String) { + println!("Texto: '{s}'"); + println!("Longitud: {}", s.len()); + println!("¿Contiene 'Rust'? {}", s.contains("Rust")); +} + +fn estadisticas(nums: &Vec) { + println!("Números: {:?}", nums); + println!("Cantidad: {}", nums.len()); + println!("Suma: {}", nums.iter().sum::()); + println!("Promedio: {:.2}", nums.iter().sum::() as f64 / nums.len() as f64); +} +``` + +### Funciones que Modifican + +```rust +fn main() { + let mut mensaje = String::from("Hola"); + let mut puntuaciones = vec![85, 90, 78]; + + // Modificar a través de referencias mutables + agregar_exclamacion(&mut mensaje); + agregar_bonus(&mut puntuaciones, 5); + + println!("Mensaje modificado: {mensaje}"); + println!("Puntuaciones con bonus: {:?}", puntuaciones); +} + +fn agregar_exclamacion(s: &mut String) { + s.push('!'); +} + +fn agregar_bonus(puntuaciones: &mut Vec, bonus: i32) { + for puntuacion in puntuaciones.iter_mut() { + *puntuacion += bonus; + } +} +``` + +### Builder Pattern con References + +```rust +struct Configuracion { + nombre: String, + debug: bool, + max_conexiones: u32, +} + +impl Configuracion { + fn nueva() -> Self { + Configuracion { + nombre: String::from("default"), + debug: false, + max_conexiones: 100, + } + } + + // Métodos que toman &mut self para modificar + fn set_nombre(&mut self, nombre: &str) -> &mut Self { + self.nombre = nombre.to_string(); + self + } + + fn set_debug(&mut self, debug: bool) -> &mut Self { + self.debug = debug; + self + } + + fn set_max_conexiones(&mut self, max: u32) -> &mut Self { + self.max_conexiones = max; + self + } + + // Método que toma &self para leer + fn mostrar(&self) { + println!("Configuración:"); + println!(" Nombre: {}", self.nombre); + println!(" Debug: {}", self.debug); + println!(" Max conexiones: {}", self.max_conexiones); + } +} + +fn main() { + let mut config = Configuracion::nueva(); + + // Chaining methods que usan referencias mutables + config + .set_nombre("MiApp") + .set_debug(true) + .set_max_conexiones(200); + + config.mostrar(); +} +``` + +## Ejercicios Prácticos + +### Ejercicio 1: Analizador de Texto + +```rust +fn main() { + let texto = String::from("Rust es un lenguaje de programación rápido y seguro"); + + // Análisis usando referencias + let info = analizar_texto(&texto); + + println!("Texto: '{texto}'"); + println!("Palabras: {}", info.0); + println!("Caracteres: {}", info.1); + println!("Palabra más larga: '{}'", info.2); + + // Modificar el texto + let mut texto_mut = texto; + agregar_emoji(&mut texto_mut); + println!("Con emoji: {texto_mut}"); +} + +fn analizar_texto(texto: &str) -> (usize, usize, &str) { + let palabras: Vec<&str> = texto.split_whitespace().collect(); + let num_palabras = palabras.len(); + let num_chars = texto.chars().count(); + + let palabra_mas_larga = palabras + .iter() + .max_by_key(|palabra| palabra.len()) + .unwrap(); + + (num_palabras, num_chars, palabra_mas_larga) +} + +fn agregar_emoji(texto: &mut String) { + texto.push_str(" 🦀"); +} +``` + +### Ejercicio 2: Manipulador de Lista + +```rust +fn main() { + let mut numeros = vec![3, 1, 4, 1, 5, 9, 2, 6]; + + println!("Lista original: {:?}", numeros); + + // Operaciones que no modifican + mostrar_estadisticas(&numeros); + let pares = obtener_pares(&numeros); + println!("Números pares: {:?}", pares); + + // Operaciones que modifican + duplicar_valores(&mut numeros); + println!("Después de duplicar: {:?}", numeros); + + filtrar_mayores_que(&mut numeros, 10); + println!("Mayores que 10: {:?}", numeros); +} + +fn mostrar_estadisticas(lista: &Vec) { + let suma: i32 = lista.iter().sum(); + let promedio = suma as f64 / lista.len() as f64; + let max = lista.iter().max().unwrap(); + let min = lista.iter().min().unwrap(); + + println!("Estadísticas:"); + println!(" Suma: {suma}"); + println!(" Promedio: {promedio:.2}"); + println!(" Máximo: {max}"); + println!(" Mínimo: {min}"); +} + +fn obtener_pares(lista: &Vec) -> Vec { + lista.iter() + .filter(|&&x| x % 2 == 0) + .cloned() + .collect() +} + +fn duplicar_valores(lista: &mut Vec) { + for valor in lista.iter_mut() { + *valor *= 2; + } +} + +fn filtrar_mayores_que(lista: &mut Vec, limite: i32) { + lista.retain(|&x| x > limite); +} +``` + +### Ejercicio 3: Parser de Configuración Simple + +```rust +fn main() { + let config_text = "nombre=MiApp\ndebug=true\nport=8080\nmax_users=1000"; + + let config = parsear_config(config_text); + mostrar_config(&config); + + // Modificar configuración + let mut config_mut = config; + actualizar_puerto(&mut config_mut, 3000); + mostrar_config(&config_mut); +} + +#[derive(Debug)] +struct Config { + nombre: String, + debug: bool, + port: u32, + max_users: u32, +} + +fn parsear_config(texto: &str) -> Config { + let mut nombre = String::new(); + let mut debug = false; + let mut port = 8080; + let mut max_users = 100; + + for linea in texto.lines() { + if let Some((clave, valor)) = parsear_linea(linea) { + match clave { + "nombre" => nombre = valor.to_string(), + "debug" => debug = valor == "true", + "port" => port = valor.parse().unwrap_or(8080), + "max_users" => max_users = valor.parse().unwrap_or(100), + _ => println!("Clave desconocida: {clave}"), + } + } + } + + Config { nombre, debug, port, max_users } +} + +fn parsear_linea(linea: &str) -> Option<(&str, &str)> { + let partes: Vec<&str> = linea.split('=').collect(); + if partes.len() == 2 { + Some((partes[0], partes[1])) + } else { + None + } +} + +fn mostrar_config(config: &Config) { + println!("Configuración:"); + println!(" Nombre: {}", config.nombre); + println!(" Debug: {}", config.debug); + println!(" Puerto: {}", config.port); + println!(" Max usuarios: {}", config.max_users); +} + +fn actualizar_puerto(config: &mut Config, nuevo_puerto: u32) { + config.port = nuevo_puerto; + println!("Puerto actualizado a: {nuevo_puerto}"); +} +``` + +## Errores Comunes con Borrowing + +### Error 1: Referencias Colgantes (Dangling References) + +```rust +// ¡ESTO NO COMPILA! +// fn crear_referencia_colgante() -> &String { +// let s = String::from("hola"); +// &s // ERROR: returns a reference to data owned by the current function +// } + +// Solución: retornar el valor, no una referencia +fn crear_string_correcta() -> String { + String::from("hola") +} + +fn main() { + let s = crear_string_correcta(); + println!("{s}"); +} +``` + +### Error 2: Múltiples Referencias Mutables + +```rust +fn main() { + let mut s = String::from("hola"); + + // ¡ESTO NO COMPILA! + // let r1 = &mut s; + // let r2 = &mut s; + // println!("{r1} {r2}"); + + // Solución: usar las referencias en diferentes scopes + { + let r1 = &mut s; + r1.push_str(" mundo"); + } // r1 sale de scope aquí + + let r2 = &mut s; // Ahora está bien + r2.push('!'); + println!("{r2}"); +} +``` + +## Puntos Clave para Recordar + +1. **Las referencias no tienen ownership** del valor que referencian +2. **`&` crea referencias inmutables**, `&mut` crea referencias mutables +3. **Solo una referencia mutable OR múltiples inmutables** a la vez +4. **Las referencias deben ser válidas** durante su uso (no dangling) +5. **Los slices son referencias** a porciones de colecciones +6. **`&str` es más flexible** que `&String` para parámetros +7. **Rust desreferencia automáticamente** en muchos contextos +8. **Las reglas de borrowing previenen** data races y use-after-free + +## Próximo Paso + +En el siguiente capítulo exploraremos las **Structs**, que nos permiten agrupar datos relacionados y crear tipos de datos personalizados, combinando ownership y borrowing de manera efectiva. \ No newline at end of file diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index ffaec8a..d0d2c3a 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -18,8 +18,124 @@ const { + - + diff --git a/src/styles/global.css b/src/styles/global.css index fd37941..82cac80 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -18,14 +18,28 @@ --color-note-bg: #4795cc; --color-note-dark-bg: #205275; --color-bg-nav: #99561426; + + /* Admonition colors */ + --color-note: #4795cc; + --color-note-bg: #1e3a5f; + --color-tip: #00d4aa; + --color-tip-bg: #003d36; + --color-important: #a855f7; + --color-important-bg: #3c1a5b; + --color-warning: #f59e0b; + --color-warning-bg: #451a03; + --color-caution: #ef4444; + --color-caution-bg: #4c1d1d; } #content-markdown { h1 { - @apply py-2 text-2xl; + font-size: 1.5rem; + padding: 1rem 0; } h2 { + margin: 0.5rem 0 0.2rem 0; color: var(--color-yellow); } @@ -43,6 +57,147 @@ } } +pre { + background-color: var(--color-editor-bg); + border: 1px solid var(--color-stroke-color); + border-radius: 0.5rem; + padding: 1rem; + margin-top: 1rem; + margin-bottom: 1rem; + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; + margin-top: 1rem; + + th, + td { + border: 1px solid var(--color-stroke-color); + padding: 0.5rem; + text-align: left; + } + + th { + background-color: var(--color-light-bg); + } +} + +p code { + background-color: var(--color-editor-bg); + padding: 0.2rem 0.4rem; + border-radius: 0.25rem; + font-family: "Fira Code", monospace; + font-size: 0.95rem; +} + +/* Admonitions/Callouts */ +#content-markdown blockquote { + margin: 1rem 0; + padding: 1rem; + border-radius: 0.5rem; + border-left: 4px solid; + position: relative; + + /* Default styling */ + background-color: var(--color-light-bg); + border-left-color: var(--color-stroke-color); +} + +#content-markdown blockquote p { + margin: 0.5rem 0; + line-height: 1.6; +} + +#content-markdown blockquote p:first-child { + font-weight: 600; + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +#content-markdown blockquote p:last-child { + margin-bottom: 0; +} + +/* Admonition types - requires JavaScript to add classes */ +#content-markdown blockquote.admonition-note { + background-color: var(--color-note-bg); + border-left-color: var(--color-note); +} + +#content-markdown blockquote.admonition-note p:first-child { + color: var(--color-note); +} + +#content-markdown blockquote.admonition-note p:first-child::before { + content: "💡"; + font-size: 1.1rem; +} + +#content-markdown blockquote.admonition-tip { + background-color: var(--color-tip-bg); + border-left-color: var(--color-tip); +} + +#content-markdown blockquote.admonition-tip p:first-child { + color: var(--color-tip); +} + +#content-markdown blockquote.admonition-tip p:first-child::before { + content: "💡"; + font-size: 1.1rem; +} + +#content-markdown blockquote.admonition-important { + background-color: var(--color-important-bg); + border-left-color: var(--color-important); +} + +#content-markdown blockquote.admonition-important p:first-child { + color: var(--color-important); +} + +#content-markdown blockquote.admonition-important p:first-child::before { + content: "❗"; + font-size: 1.1rem; +} + +#content-markdown blockquote.admonition-warning { + background-color: var(--color-warning-bg); + border-left-color: var(--color-warning); +} + +#content-markdown blockquote.admonition-warning p:first-child { + color: var(--color-warning); +} + +#content-markdown blockquote.admonition-warning p:first-child::before { + content: "⚠️"; + font-size: 1.1rem; +} + +#content-markdown blockquote.admonition-caution { + background-color: var(--color-caution-bg); + border-left-color: var(--color-caution); +} + +#content-markdown blockquote.admonition-caution p:first-child { + color: var(--color-caution); +} + +#content-markdown blockquote.admonition-caution p:first-child::before { + content: "🚨"; + font-size: 1.1rem; +} + +/* Hide the [!TYPE] marker text */ +#content-markdown blockquote p:first-child strong:first-of-type { + display: none; +} + * { scrollbar-width: thin; scrollbar-color: var(--color-editor-bg) transparent;