El Ownership es el sistema que asigna un propietario a cada valor y determina cuándo debe destruirse.
Bienvenido a la propiedad. Es uno de los conceptos más importantes de Rust y permite gestionar recursos sin necesitar un recolector de basura.
En la mayoría de lenguajes, no piensas en “de quién es este dato”.
- En Python: “Creo una lista y el recolector de basura ya la borrará cuando nadie la mire”.
- En C: “Pido memoria, y tengo que acordarme de liberarla yo mismo o explota”.
En Rust, cada valor tiene un “dueño” y el compilador controla cuándo cambia esa propiedad y cuándo debe destruirse el valor.
El ámbito (scope)
Antes de ver las reglas, debemos entender el concepto de Scope (ámbito). El ámbito es, básicamente, el rango dentro del código donde una variable es válida.
En Rust, el ámbito suele estar delimitado por llaves {}.
fn main() { // Inicio del ámbito de main
{ // Inicio de un ámbito interno
let s = "hola"; // 's' nace aquí y es válida
println!("{}", s);
} // Fin del ámbito interno. 's' deja de ser válida aquí.
// println!("{}", s); // ❌ ¡Error! 's' ya no existe.
}Esto parece obvio en variables simples del Stack (como vimos en el artículo anterior). Pero la gracia de Rust es que aplica esta lógica de ámbitos para gestionar la memoria del Heap.
Las reglas del ownership
El compilador verifica que el código cumpla estas tres reglas:
- Cada valor en Rust tiene un
owner(propietario). - Solo puede haber un
ownera la vez. - Cuando el
ownersale del ámbito (scope), el valor se eliminará.
Vamos a desgranarlas.
Un dueño y limpieza automática
Imagina que usamos un tipo String, que gestiona un búfer de tamaño dinámico en el Heap.
fn main() {
{
let s = String::from("texto en el heap"); // s es el OWNER
// Se ha pedido memoria en el Heap.
}
// Aquí se cierra la llave.
// s sale del ámbito.
// Rust llama automáticamente a 'drop'. La memoria se libera.
}No has tenido que escribir free ni delete. Tampoco hay un Garbage Collector vigilando. Simplemente, Rust inserta una llamada a una función especial llamada drop justo donde termina el ámbito del dueño.
Esto se conoce en C++ como RAII (Resource Acquisition Is Initialization), pero en Rust es obligatorio y el compilador lo fuerza.
Solo un dueño a la vez
En este punto la cabeza nos suele hacer “clic”.
En otros lenguajes, puedes tener múltiples variables apuntando al mismo objeto en memoria sin problemas.
// JavaScript
let a = [1, 2, 3];
let b = a; // Ambos apuntan al mismo array
// Si modifico 'b', 'a' también cambia.En Rust, esto violaría la regla de “un solo propietario”. ¿Por qué?
Porque si a y b apuntaran al mismo sitio en el Heap, cuando ambos salieran del ámbito, ambos intentarían liberar la misma memoria. Eso es un error gravísimo llamado Double Free, que corrompe la memoria y crea agujeros de seguridad.
Por tanto, en Rust pasa esto:
let s1 = String::from("hola");
let s2 = s1; // ¡CAMBIO DE DUEÑO!
println!("{}", s1); // ❌ Error: value borrowed here after moveCuando hacemos let s2 = s1, Rust dice: “Vale, s2 es el nuevo jefe. s1, estás despedido, ya no vales nada”.
Rust invalida la primera variable. Ya no puedes usar s1. Esto garantiza que, cuando termine la función, solo s2 intentará liberar la memoria. Problema de seguridad resuelto.
String y los tipos Copy
Quizás te estés preguntando: “Espera, en el artículo de variables hicimos let x = 5; let y = x; y podíamos usar los dos. ¿Me has mentido?”
No. La diferencia real es que algunos tipos implementan el trait Copy, mientras que otros no.
Los enteros, flotantes, booleanos y caracteres implementan Copy. Al asignarlos, Rust copia automáticamente el valor, de modo que ambos nombres conservan datos independientes.
let x = 5;
let y = x; // Se hace una copia completa.
println!("x: {}, y: {}", x, y); // ✅ Funciona.Los String y Vec<T> no implementan Copy, porque una copia implícita de su representación dejaría dos propietarios de la misma reserva. Al asignarlos, Rust transfiere la propiedad (lo que llamamos un move).
Copy no significa simplemente “vive en el stack”. Una referencia compartida puede ser Copy, y un tipo formado solo por datos de tamaño fijo puede no serlo. Lo que manda es si el tipo implementa el trait Copy.
Una imagen mental del ownership
Para visualizar el Ownership, piensa en la memoria del Heap como un maletín lleno de dinero.
- Regla 1: El maletín debe estar esposado a la muñeca de alguien (
owner). - Regla 2: Si quieres darle el maletín a otra persona, tienes que quitarte las esposas y ponérselas a él. Tú te quedas sin maletín. No pueden llevarlo dos personas a la vez.
- Regla 3: Si la persona con el maletín sale de la habitación (ámbito), el maletín se destruye.
El sistema de ownership es parte del precio a pagar por tener control sobre los recursos con garantías de seguridad en código seguro.
- Nos obliga a pensar: “¿Quién posee este dato ahora?”.
- Libera de forma determinista los recursos ligados a sus propietarios.
- Impide usar memoria ya liberada desde código Rust seguro.
Puede parecer restrictivo que let s2 = s1 invalide s1, pero en realidad nos está salvando de bugs muy difíciles de detectar.