Saltar al contenido

Lección 1 de 19 · Lectura y práctica

Revisión de Ownership y Borrowing Avanzado

Concepto claveEn sistemas de baja latencia y alta seguridad, el ownership y borrowing de Rust no son solo mecanismos de seguridad de memoria, sino herramientas de optimizacion de rendimiento. El ownership garantiza que cada dato.

Del concepto a la práctica

Taller de esta lección

La regla decisiva es separar una fase de lectura de una fase de modificación: una referencia mutable exclusiva no puede coexistir con referencias inmutables al mismo dato.

Ejemplo resuelto

Para actualizar una orden sin copiarla, una función puede recibir &mut Vec<Orden> y devolver el importe calculado. La colección conserva ownership y la función solo opera durante la llamada.

rust
fn importe(ordenes: &mut Vec<(u32, u32)>) -> u32 {
    ordenes.push((101, 3));
    ordenes.last().unwrap().0 * ordenes.last().unwrap().1
}

Pruébalo

  1. Inicializa la colección con (100, 2).

  2. Llama a importe y guarda su retorno.

  3. Imprime el retorno y la longitud de la colección.

Comprueba tu resultado

La salida debe ser 303 y 2.

Error frecuente: conservar ordenes.last() mientras se intenta hacer push; corrección: termina el préstamo, o usa un bloque separado.

Ejercicio: añade (99, 4) y calcula el importe del último elemento. Resultado esperado: 396, con tres elementos y sin copiar toda la colección.

La función agrega una orden101x3 cada vez que se llama; para obtener396 añade99x4 y calcula ese último elemento sin volver a llamar importe, que agregaría otro101x3. El ejemplo usa u32 pequeños; para precios/cantidades arbitrarios valida límites o checked_mul. El préstamo compartido puede coexistir antes o después del mutable cuando ya no se usa, y regiones disjuntas pueden prestarse con APIs que lo expresen.

Diagrama de decisión con ramas Sí y No y un bucle que procesa cada elemento de una lista.
CursaloMapa de práctica
Cómo avanza un programa

Una condición decide qué camino ejecuta el programa. Un bucle repite una operación mientras queden elementos, y termina cuando la lista se agota. Separar ambas ideas ayuda a seguir el flujo y comprobar casos límite.

Ver la infografía en tamaño completo ↗
Fuentes para profundizar

Concepto clave

En sistemas de baja latencia y alta seguridad, el ownership y borrowing de Rust no son solo mecanismos de seguridad de memoria, sino herramientas de optimizacion de rendimiento. El ownership garantiza que cada dato tenga un unico dueno en tiempo de compilacion, eliminando la necesidad de recolector de basura y permitiendo prediccion de tiempos de ejecucion. El borrowing, con sus reglas de prestamo mutable e inmutable, evita condiciones de carrera sin costos de sincronizacion en tiempo de ejecucion.

Para sistemas criticos, entender como estas reglas se traducen en patrones de acceso a memoria es crucial. Un prestamo mutable exclusivo (&mut T) no solo previene data races, sino que permite al compilador realizar optimizaciones agresivas, como reordenar instrucciones o eliminar verificaciones de limites, al saber que no hay otras referencias activas. Esto es analogo a un sistema de control de trafico aereo: solo un avion puede tener control exclusivo de una pista en un momento dado, garantizando seguridad y eficiencia.

Como funciona en la practica

Considera un sistema de procesamiento de transacciones financieras donde la latencia es critica. Implementaremos un buffer circular para almacenar transacciones pendientes, usando ownership para garantizar acceso seguro sin locks.

struct CircularBuffer {
    data: Vec>,
    head: usize,
    tail: usize,
}

impl CircularBuffer {
    fn new(capacity: usize) -> Self {
        CircularBuffer {
            data: (0..capacity).map(|_| None).collect(),
            head: 0,
            tail: 0,
        }
    }
    
    fn push(&mut self, item: T) -> Result<(), &'static str> {
        let next_tail = (self.tail + 1) % self.data.len();
        if next_tail == self.head {
            return Err("Buffer lleno");
        }
        self.data[self.tail] = Some(item);
        self.tail = next_tail;
        Ok(())
    }
    
    fn pop(&mut self) -> Option {
        if self.head == self.tail {
            return None;
        }
        let item = self.data[self.head].take();
        self.head = (self.head + 1) % self.data.len();
        item
    }
    
    fn peek(&self) -> Option<&T> {
        self.data[self.head].as_ref()
    }
}

Observa como push y pop requieren &mut self, garantizando acceso exclusivo durante modificaciones, mientras peek usa &self, permitiendo multiples lecturas concurrentes. El compilador optimiza esto a acceso directo a memoria sin overhead de sincronizacion.

Caso de estudio

En un sistema de trading de alta frecuencia, procesamos 100,000 operaciones por segundo con latencia maxima de 50 microsegundos. Usamos un patron de zero-copy parsing con borrowing avanzado para evitar asignaciones de memoria.

fn parse_trade_message<'a>(data: &'a [u8]) -> Result, ParseError> {
    if data.len() < 24 {
        return Err(ParseError::InsufficientData);
    }
    let symbol = std::str::from_utf8(&data[0..8])?;
    let price = f64::from_le_bytes(data[8..16].try_into().unwrap());
    let quantity = i64::from_le_bytes(data[16..24].try_into().unwrap());
    
    Ok(TradeData {
        symbol,
        price,
        quantity,
        raw_data: &data[0..24],
    })
}

struct TradeData<'a> {
    symbol: &'a str,
    price: f64,
    quantity: i64,
    raw_data: &'a [u8],
}

Este codigo:

  • Usa lifetimes explicitos ('a) para garantizar que las referencias a symbol y raw_data no sobrevivan a los datos originales
  • Evita copiar datos al tomar slices (&data[0..8]) en lugar de crear nuevos strings o arrays
  • Permite al compilador optimizar el acceso a memoria al conocer los limites de las referencias
En benchmarks reales, este enfoque reduce la latencia de parsing en un 40% comparado con versiones que asignan memoria dinamicamente.

Errores comunes

  1. Abuso de clones para evitar problemas de borrowing: En sistemas de baja latencia, cada clone implica una asignacion de memoria y copia. Solucion: Reestructurar el codigo para usar referencias o transferir ownership cuando sea posible.
  2. Lifetimes innecesariamente largos: Mantener referencias vivas mas tiempo del necesario limita las optimizaciones del compilador. Solucion: Usar scopes mas pequenos y tipos como Arc o Rc solo cuando sea estrictamente necesario.
  3. Ignorar el aliasing con mutabilidad: En sistemas concurrentes, pasar &mut entre hilos sin proteccion causa undefined behavior. Solucion: Usar primitivas de sincronizacion como Mutex o canales (mpsc) cuando se necesita mutabilidad compartida.
  4. No aprovechar el borrow checker para invariantes de seguridad: El borrowing puede garantizar invariantes como "un recurso no puede ser liberado mientras esta en uso". Solucion: Disenar APIs que usen tipos como Pin o lifetimes para codificar estas garantias.

Checklist de dominio

  • Puedo explicar como el sistema de ownership elimina la necesidad de garbage collector en sistemas de tiempo real
  • Se disenar estructuras de datos que minimicen las copias usando borrowing y slices
  • Puedo usar lifetimes explicitos para garantizar seguridad sin overhead en tiempo de ejecucion
  • Se identificar cuando usar Rc/Arc vs borrowing basado en lifetimes
  • Puedo optimizar patrones de acceso a memoria basandome en las reglas de borrowing
  • Se implementar zero-copy deserialization para datos de alta frecuencia
  • Puedo debuggear errores de borrowing complejos en sistemas concurrentes

Optimizacion de un sistema de mensajeria de baja latencia

Implementa un sistema de mensajeria para un sistema de trading que procese 1,000,000 de mensajes por segundo con latencia submicrosegundo.

  1. Crea una estructura MessageQueue que almacene mensajes usando un buffer circular pre-asignado
  2. Implementa metodos enqueue y dequeue que usen borrowing para evitar copias
  3. Asegurate de que multiples productores puedan enviar mensajes concurrentemente usando canales MPSC
  4. Implementa un consumidor que procese mensajes en batches para mejorar locality de cache
  5. Agrega metricas de latencia percentil 99 (p99) para monitorear el rendimiento

Requisitos tecnicos:

  • No uses asignaciones de memoria (alloc) durante el procesamiento normal
  • Garantiza que no haya data races sin usar locks pesados
  • Mantiene la latencia p99 por debajo de 5 microsegundos
Pistas
  • Considera usar un array de tamanio fijo con indices atomicos para el buffer circular
  • Los canales de Rust (std::sync::mpsc) ya estan optimizados para baja latencia
  • Procesar en batches reduce el overhead de llamadas a funcion y mejora prefetching

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.

Práctica acompañada: Seguridad desde el primer hábito

Cuentas, copias y revisión defensiva. Usa este cuaderno como práctica complementaria de la ruta; el material original de la lección continúa siendo tu referencia.

Cuentas, copias y revisión defensiva: Inventario, Protección, Copia, Verificación
De la idea al resultado: una secuencia para practicar y revisar.

Práctica: recupera un archivo

Crea tres documentos de prueba y una copia separada. Modifica uno y recupera la versión anterior en otra carpeta. Abre el archivo restaurado y comprueba su contenido; una notificación de copia completada no demuestra que la recuperación funcione.

Anota fecha, ubicación, versión recuperada y resultado. Si usas un laboratorio web como OWASP Juice Shop, mantenlo en una red local o entorno de entrenamiento y sigue sus instrucciones oficiales. Documenta el hallazgo y su mitigación. No traslades las pruebas a servicios externos o cuentas ajenas para completar una actividad.

Comprueba tu entrega

Entrega un inventario sin secretos, una lista de mejoras realizadas y la evidencia de restauración de tus archivos de prueba. Elige una fecha de revisión y un responsable para cada pendiente. No declares una cuenta «segura para siempre»: la configuración y el contexto pueden cambiar.

Sigue con Rust para Sistemas de Baja Latencia y Alta Seguridad

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.