Dominando las colecciones fundamentales en Rust: Vec, String y HashMap

Las colecciones en la biblioteca estándar de Rust son herramientas esenciales para manejar datos cuyo tamaño no se conoce en tiempo de compilación. A diferencia de los arrays nativos, estas estructuras se almacenan en el heap (montículo), lo que permite que crezcan o disminuyan dinámicamente según las necesidades de la aplicación.

1. El vector dinámico: Vec<T>

El tipo Vec<T> es la colección más utilizada. Permite almacenar una secuencia contigua de elementos del mismo tipo con acceso indexado en tiempo constante O(1).

Estructura en memoria

Un vector consta de tres partes almacenadas en el stack: un puntero a los datos en el heap, la longitud actual (cuántos elementos contiene) y la capacidad (cuánto espacio total se ha reservado).

// Estructura interna simplificada
struct Vec<T> {
   ptr: *mut T,       // Dirección en el heap
   len: usize,        // Elementos actuales
   cap: usize,        // Espacio total disponible
}

Opreaciones comunes con Vectores

fn ejemplo_vectores() {
   // Inicialización con capacidad predefinida para optimizar rendimiento
   let mut edades: Vec<i32> = Vec::with_capacity(5);

   // Uso de la macro vec!
   let mut precios = vec![19.99, 25.50, 9.99];

   // Inserción de elementos
   precios.push(45.00); 
   precios.insert(1, 15.00); // Mueve los elementos siguientes (costoso: O(n))

   // Acceso seguro mediante get
   if let Some(precio) = precios.get(2) {
       println!("El tercer precio es: {}", precio);
   }

   // Iteración mutable para aplicar descuentos
   for p in precios.iter_mut() {
       *p *= 0.90; // 10% de descuento
   }

   // Filtrado eficiente
   precios.retain(|&p| p > 10.0);
}

2. Manejo de texto: String y &str

Rust separa las cadenas de texto en dos tipos principales para garantziar la seguridad de la memoria y la codificación UTF-8 válida.

  • &str: Un "slice" de cadena, es una referencia inmutable a una secuencia de bytes UTF-8.
  • String: Una cadena con propiedad (owned), modificable y con capacidad de crecimiento, que gestiona su propia memoria en el heap.

Manipulación de String

fn manejo_texto() {
   // Creación desde un literal
   let mut mensaje = String::from("Hola");
   
   // Concatenación
   mensaje.push_str(" Mundo");
   mensaje.push('!');

   // Uso de format! para construir cadenas complejas sin perder propiedad
   let usuario = "Alex";
   let log = format!("Acceso detectado: {}", usuario);

   // Longitud: bytes vs caracteres
   let texto_utf8 = "Rust 🦀";
   println!("Bytes: {}", texto_utf8.len()); // 9 bytes (el emoji ocupa 4)
   println!("Caracteres: {}", texto_utf8.chars().count()); // 6 caracteres
}

Errores frecuentes con cadenas

Intentar indexar una cadena como mensaje[0] fallará en Rust. Debido a que UTF-8 usa longitud variable, el primer "carácter" podría no ser el primer byte. Se debe usar el iterador chars() o manejar slices con precaución.

3. Mapas de Hash: HashMap<K, V>

HashMap almacena pares clave-valor. Es ideal para búsquedas rápidas donde se utiliza una clave única para recuperar un dato asociado.

Uso eficiente de HashMap

use std::collections::HashMap;

fn gestion_inventario() {
   let mut stock = HashMap::new();

   // Insertar datos
   stock.insert(String::from("Laptop"), 10);
   stock.insert(String::from("Monitor"), 25);

   // Actualización inteligente con Entry API
   // Si la clave no existe, inserta 0 y luego suma 1
   let cantidad_ratones = stock.entry(String::from("Mouse")).or_insert(0);
   *cantidad_ratones += 5;

   // Búsqueda segura
   match stock.get("Laptop") {
       Some(unidades) => println!("Unidades disponibles: {}", unidades),
       None => println!("Producto no registrado"),
   }
}

Comparativa de errores comunes

Error Común Consecuencia Solución Recomendada
Acceso por índice fuera de límites Panic (caída del programa) Usar el método .get() que devuelve Option
Modificar un Vec mientras se itera Error de compilación (Borrow checker) Coleccionar cambios primero o usar retain
Confundir bytes con caracteres en String Lógica incorrecta o pánico Usar .chars() para lógica de texto
Re-hashear constantemente en HashMap Bajo rendimiento Usar with_capacity si se conoce el volumen de datos

Implementación Práctica: Sistema de Inventario Escolar

A continuación, se presenta un ejemplo que integra las tres estructuras para gestionar una base de datos de estudiantes y sus calificaciones.

use std::collections::HashMap;

struct Alumno {
   nombre: String,
   notas: Vec<f32>,
}

struct RegistroEscolar {
   // Usamos el ID como clave para acceso O(1)
   estudiantes: HashMap<u32, Alumno>,
}

impl RegistroEscolar {
   fn nuevo() -> Self {
       Self { estudiantes: HashMap::new() }
   }

   fn inscribir(&mut self, id: u32, nombre: &str) {
       let nuevo_alumno = Alumno {
           nombre: nombre.to_string(),
           notas: Vec::new(),
       };
       self.estudiantes.entry(id).or_insert(nuevo_alumno);
   }

   fn agregar_nota(&mut self, id: u32, nota: f32) -> Result<(), String> {
       let alumno = self.estudiantes.get_mut(&id)
           .ok_or_else(|| format!("ID {} no encontrado", id))?;
       
       alumno.notas.push(nota);
       Ok(())
   }

   fn promedio_general(&self) {
       for (id, alumno) in &self.estudiantes {
           let suma: f32 = alumno.notas.iter().sum();
           let count = alumno.notas.len();
           let promedio = if count > 0 { suma / count as f32 } else { 0.0 };
           
           println!("ID: {} | Estudiante: {} | Promedio: {:.2}", 
                    id, alumno.nombre, promedio);
       }
   }
}

fn main() {
   let mut sistema = RegistroEscolar::nuevo();
   
   sistema.inscribir(101, "Sofia");
   sistema.inscribir(102, "Mateo");

   let _ = sistema.agregar_nota(101, 9.5);
   let _ = sistema.agregar_nota(101, 8.0);
   let _ = sistema.agregar_nota(102, 7.0);

   sistema.promedio_general();
}

Este sistema demuestra cómo el HashMap facilita la búsqueda por identificador único, el Vec permite almacenar una lista variable de notas y el String gestiona los nombres de forma segura.

Etiquetas: Rust backend data-structures memory-management

Publicado el 8-2 22:12