Transferir la propiedad a cada función que necesita leer un dato es tedioso. El mecanismo para evitarlo se llama borrowing: puedes prestar una referencia a un valor sin transferir la propiedad. El propietario original sigue siendo válido y el valor se destruye cuando él sale de scope, no cuando lo hace quien lo tomó prestado.
Referencias inmutables con &
fn calcular_longitud(s: &String) -> usize {
s.len()
} // s es una referencia; al salir del scope NO se destruye el String
fn main() {
let s1 = String::from("hola");
let len = calcular_longitud(&s1); // prestamos s1
println!("'{}' tiene {} caracteres", s1, len); // s1 sigue válido
}
La & delante del tipo indica "referencia". La referencia apunta al valor pero no lo posee. Cuando calcular_longitud termina, la referencia desaparece, pero el String original sigue intacto.
Las dos reglas del borrow checker
El compilador impone dos reglas sobre las referencias en cualquier scope:
- Puedes tener cualquier número de referencias inmutables (
&T). - O bien una sola referencia mutable (
&mut T), pero no las dos cosas a la vez.
Esto elimina las carreras de datos (data races) en tiempo de compilación.
Múltiples referencias inmutables: OK
fn main() {
let s = String::from("hola");
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{} {} {}", r1, r2, r3); // OK: todas leen, ninguna escribe
}
Referencia mutable con &mut
fn añadir_mundo(s: &mut String) {
s.push_str(", mundo");
}
fn main() {
let mut s = String::from("hola");
añadir_mundo(&mut s);
println!("{}", s); // "hola, mundo"
}
Para tomar una referencia mutable, el valor original también debe ser mut.
Conflicto: inmutable y mutable a la vez
fn main() {
let mut s = String::from("hola");
let r1 = &s; // OK
let r2 = &s; // OK
let r3 = &mut s; // ERROR: ya existe r1 y r2
println!("{} {} {}", r1, r2, r3);
}
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:6:14
|
4 | let r1 = &s;
| -- immutable borrow occurs here
5 | let r2 = &s;
6 | let r3 = &mut s;
| ^^^^^^^ mutable borrow occurs here
7 |
8 | println!("{} {} {}", r1, r2, r3);
| -- immutable borrow later used here
Los scopes resuelven el conflicto
Con Non-Lexical Lifetimes (NLL), el borrow checker sabe cuándo se usa por última vez cada referencia. Si las referencias inmutables ya no se usan, puedes crear la mutable:
fn main() {
let mut s = String::from("hola");
let r1 = &s;
let r2 = &s;
println!("{} {}", r1, r2); // último uso de r1 y r2
// Aquí r1 y r2 ya no están "vivas"
let r3 = &mut s; // OK
r3.push_str(", mundo");
println!("{}", r3);
}
Dos referencias mutables: también prohibido
fn main() {
let mut s = String::from("hola");
let r1 = &mut s;
let r2 = &mut s; // ERROR
println!("{} {}", r1, r2);
}
error[E0499]: cannot borrow `s` as mutable more than once at a time
Esta regla impide las data races: si dos referencias mutables al mismo dato existieran simultáneamente en distintos hilos, ninguno de los dos sabría qué valor encontraría.
Resumen de las cuatro situaciones
| Situación | ¿Permitido? |
|---|---|
| N referencias inmutables simultáneas | Sí |
| 1 referencia mutable sola | Sí |
| 1 mutable + 1 inmutable simultáneas | No |
| 2 referencias mutables simultáneas | No |
Resumen
&Tpresta un valor sin moverlo. Puedes tener tantas como quieras mientras no haya una mutable activa.&mut Tpresta con permiso de modificar. Solo puede existir una a la vez, y excluye todas las inmutables.- El compilador usa NLL para calcular el tiempo de vida real de cada referencia, no solo el lexical scope.
El mecanismo que hace cumplir estas reglas es el borrow checker, que analizamos en el siguiente artículo junto con los errores más frecuentes que produce y cómo resolverlos.
