El sistema de ownership en Rust se basa en tres principios fundamentales:
1. Cada valor en Rust tiene un propietario.
2. Solo puede haber un propietario a la vez.
3. Cuando el propietario sale del alcance (scope), el valor se elimina.
Estos principios ayudan a garantizar que la memoria sea gestionada de manera eficiente y segura, evitando errores comunes como los punteros colgantes y las fugas de memoria.
Cuando declaras una variable en Rust, se convierte en el propietario del valor asignado a ella. Por ejemplo:
fn main() {
let x = 5;
// x es el propietario del valor 5
}
En este caso, x es el propietario del valor 5. Cuando x sale del alcance al final del bloque main, el valor 5 se elimina automáticamente.
Rust utiliza el concepto de "move semantics" para transferir la propiedad de un valor de una variable a otra. Cuando se mueve un valor, la variable original ya no es el propietario y no puede ser utilizada. Ejemplo:
fn main() {
let s1 = String::from("hello");
let s2 = s1;
// s1 ya no es válido
println!("{}", s2); // Funciona
// println!("{}", s1); // Error: s1 no es válido
}
En este caso, la propiedad del String se mueve de s1 a s2, y s1 ya no es válido.
A veces, necesitas acceder a un valor sin tomar su propiedad. Rust permite el préstamo de valores mediante referencias. Hay dos tipos de referencias: referencias inmutables y referencias mutables.
Las referencias inmutables permiten leer un valor sin modificarlo:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("La longitud de '{}' es {}", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
En este ejemplo, &s1 crea una referencia inmutable a s1 que se pasa a la función calculate_length.
Las referencias mutables permiten modificar un valor:
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
Aquí, &mut s crea una referencia mutable que se pasa a la función change, permitiendo modificar el String.
Rust impone reglas estrictas para el borrowing para garantizar la seguridad:
1. Puedes tener cualquier número de referencias inmutables, pero solo una referencia mutable a un valor en un momento dado.
2. Las referencias deben ser siempre válidas.
Estas reglas evitan condiciones de carrera y garantizan la seguridad en el acceso concurrente a datos.
Las lifetimes en Rust aseguran que las referencias sean válidas mientras se necesiten. Aunque Rust generalmente puede inferir las lifetimes, a veces es necesario anotarlas explícitamente.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
En este ejemplo, 'a es una lifetime que indica que la referencia devuelta será válida mientras ambas referencias x e y sean válidas.
El ownership también se aplica a las colecciones en Rust, como vectores y mapas hash. Al añadir un valor a una colección, la colección se convierte en el propietario del valor.
fn main() {
let v = vec![1, 2, 3];
let v2 = v;
// v ya no es válido
println!("{:?}", v2);
}
Aquí, la propiedad del vector v se mueve a v2, y v ya no es válido.
El sistema de ownership de Rust también facilita la escritura de código concurrente seguro. Al usar el ownership y las referencias, Rust puede prevenir condiciones de carrera y otros problemas de concurrencia.
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("{:?}", v);
});
handle.join().unwrap();
}
En este caso, move transfiere la propiedad del vector v al hilo, asegurando que no haya problemas de concurrencia.
Jorge García
Fullstack developer