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