El sistema de ownership (propiedad) es el núcleo que permite a Rust garantizar la seguridad de la memoria sin necesidad de un recolector de basura (Garbage Collector). Este sistema se rige por tres reglas fundamentales que el compilador verifica estrictamente mediante un análisis estático en tiempo de compilación:

  1. Cada valor tiene exactamente un dueño (owner): En cualquier momento dado, hay una única variable responsable de ese valor. No puede haber múltiples dueños para un mismo recurso en memoria.
  2. Solo puede haber un dueño a la vez: Cuando asignas un valor a una nueva variable, la propiedad se transfiere (se realiza un move). La variable original deja de ser válida. Esto evita el error de double free (intentar liberar la misma memoria dos veces).
  3. Cuando el dueño sale de su ámbito (scope), el valor es destruido: Al finalizar el bloque de código {} donde reside el dueño, Rust llama automáticamente al método drop (mediante el trait Drop), liberando la memoria asociada.

Si estas reglas no se cumplieran, ocurrirían errores críticos como memory leaks (fugas de memoria por no liberar recursos) o use-after-free (intentar usar una dirección de memoria que ya fue liberada). Gracias a este modelo, Rust elimina estos errores en la fase de compilación sin penalizar el rendimiento en ejecución.

fn main() {
    // Regla 1: s1 es la única dueña de este String
    let s1 = String::from("Hola Rust");

    // Regla 2: El ownership se mueve de s1 a s2
    // s1 ya no es válida y no puede ser usada
    let s2 = s1;

    println!("s2 tiene el valor: {}", s2);

    // Creamos un nuevo ámbito para demostrar la Regla 3
    {
        // Regla 1: s3 es la dueña de este nuevo String
        let s3 = String::from("Ámbito interno");
        println!("s3 vive aquí: {}", s3);
        // Al llegar aquí, s3 sale de scope y se ejecuta su destructor
    } 
    // s3 ya no existe en este punto

    // Regla 2: Pasamos el ownership de s2 a la función
    take_ownership(s2);

    // println!("{}", s2); // ERROR: s2 ya no tiene el ownership
}

fn take_ownership(un_valor: String) {
    // un_valor es la dueña de la cadena aquí
    println!("La función recibió: {}", un_valor);
} // Aquí un_valor sale de scope y su memoria se libera automáticamente

Explicación del Código

  • let s1 = String::from("Hola Rust");: Se crea un objeto String en el heap. En este punto, s1 es la única dueña (Regla 1).
  • let s2 = s1;: Se produce un movimiento (move). El ownership de la cadena pasa de s1 a s2. Como resultado, s1 queda invalidada para evitar que ambas intenten liberar la misma memoria al terminar.
  • println!("{}", s2);: Accedemos al valor a través de s2, su nuevo y único dueño.
  • let s3 = String::from("Ámbito interno");: s3 se declara dentro de un bloque de código delimitado por llaves {}.
  • Al cerrar la llave }, s3 sale de su ámbito. Se aplica la Regla 3: el compilador libera automáticamente la memoria de s3.
  • take_ownership(s2);: Llamamos a la función pasando s2. El ownership se transfiere de la variable s2 (en main) a la variable un_valor (en la función).
  • fn take_ownership(un_valor: String): La variable un_valor asume la propiedad del dato. Al finalizar la ejecución de esta función, un_valor sale de su scope y su memoria es liberada.
  • // println!("{}", s2);: Este código fallaría al compilar porque s2 ya no es la dueña del valor; la propiedad fue movida hacia un_valor.

24