Rust: Ownership
Descubre el sistema de ownership en Rust y cómo garantiza seguridad y eficiencia en la gestión de memoria sin recolector de basura.
Aprende Rust GRATIS y certifícateOwnership en Rust
El ownership (propiedad) es el concepto fundamental que distingue a Rust de otros lenguajes de programación. Este sistema permite que Rust garantice la seguridad de memoria sin necesidad de un recolector de basura, eliminando errores comunes como los accesos a memoria liberada o las fugas de memoria.
El sistema de propiedad
En Rust, cada valor tiene un único propietario (owner) en cualquier momento dado. Cuando el propietario sale de su ámbito de ejecución, el valor se libera automáticamente. Este mecanismo resuelve uno de los problemas más complejos en programación de sistemas: la gestión manual de memoria.
fn main() {
let s = String::from("hola"); // s es el propietario de la cadena
println!("{}", s);
} // s sale del ámbito, la memoria se libera automáticamente
El compilador de Rust verifica estas reglas en tiempo de compilación, lo que significa que los errores de memoria se detectan antes de que el programa se ejecute.
Reglas fundamentales del ownership
El sistema se basa en tres reglas inmutables:
- Cada valor tiene exactamente un propietario
- Solo puede existir un propietario a la vez
- Cuando el propietario sale del ámbito, el valor se destruye
Estas reglas se aplican tanto a tipos primitivos como a tipos más complejos, aunque con comportamientos diferentes según si implementan el trait Copy
.
fn main() {
let x = 5; // x posee el valor 5
let y = x; // se copia el valor (i32 implementa Copy)
let s1 = String::from("mundo");
let s2 = s1; // s1 transfiere la propiedad a s2
// println!("{}", s1); // Error: s1 ya no es válido
}
Transferencia de propiedad (move)
Cuando asignamos una variable que contiene datos en el heap a otra variable, ocurre una transferencia de propiedad o move. La variable original deja de ser válida para evitar la doble liberación de memoria.
fn tomar_propiedad(cadena: String) {
println!("Recibí: {}", cadena);
} // cadena se destruye aquí
fn main() {
let mi_string = String::from("ejemplo");
tomar_propiedad(mi_string);
// mi_string ya no es válido después de la llamada
}
Este comportamiento es diferente al de lenguajes como C++ o Java, donde las asignaciones típicamente crean copias superficiales que pueden llevar a problemas de memoria.
Devolución de propiedad
Las funciones pueden devolver la propiedad de los valores, permitiendo que el código llamador recupere el control sobre los datos:
fn crear_string() -> String {
let s = String::from("creada en función");
s // devuelve la propiedad de s
}
fn procesar_y_devolver(mut s: String) -> String {
s.push_str(" procesada");
s // devuelve la propiedad modificada
}
fn main() {
let cadena1 = crear_string();
let cadena2 = procesar_y_devolver(cadena1);
println!("{}", cadena2);
}
Clonación explícita
Cuando necesitamos crear una copia profunda de datos en el heap, utilizamos el método clone()
. Esta operación es explícita y potencialmente costosa:
fn main() {
let s1 = String::from("original");
let s2 = s1.clone(); // copia profunda explícita
println!("s1: {}, s2: {}", s1, s2); // ambas son válidas
}
La clonación debe usarse conscientemente, ya que implica asignación de memoria adicional y copia de datos.
Tipos que implementan Copy
Los tipos primitivos como enteros, flotantes y booleanos implementan el trait Copy
, lo que significa que se copian automáticamente en lugar de moverse:
fn main() {
let x = 42;
let y = x; // x se copia, no se mueve
println!("{} {}", x, y); // ambos son válidos
let tupla = (5, true);
let otra_tupla = tupla; // se copia porque todos sus elementos implementan Copy
println!("{:?} {:?}", tupla, otra_tupla);
}
Los tipos que contienen datos en el heap, como String
o Vec<T>
, no pueden implementar Copy
porque requerirían asignación de memoria para cada copia.
Ownership en estructuras de datos
Las estructuras personalizadas siguen las mismas reglas de ownership. Cuando una estructura contiene tipos que no implementan Copy
, la estructura completa se mueve:
struct Persona {
nombre: String,
edad: u32,
}
fn main() {
let persona1 = Persona {
nombre: String::from("Ana"),
edad: 30,
};
let persona2 = persona1; // persona1 se mueve a persona2
// println!("{}", persona1.nombre); // Error: persona1 ya no es válida
println!("{}", persona2.nombre); // Válido
}
El sistema de ownership en Rust transforma la gestión de memoria de una responsabilidad manual propensa a errores en un conjunto de reglas verificadas automáticamente por el compilador, proporcionando tanto seguridad como rendimiento.
Lecciones de este módulo de Rust
Lecciones de programación del módulo Ownership del curso de Rust.
Ejercicios de programación en este módulo de Rust
Evalúa tus conocimientos en Ownership con ejercicios de programación Ownership de tipo Test, Puzzle, Código y Proyecto con VSCode.