Referencias y Borrowing
Mover un valor cada vez que lo usas haría de Rust un lenguaje insoportable — las referencias te dejan tomar prestado el acceso sin tomar la propiedad, y el compilador lo garantiza gratis.
La lección anterior terminó con un problema real: si pasar un String a una función mueve su propiedad, y quien la llamó pierde el acceso después, entonces cualquier función que solo quisiera leer un valor te obligaría a elegir entre cederlo para siempre o clonarlo por precaución. Ninguna de las dos opciones es aceptable para algo que haces todo el tiempo — clonar en todos lados oculta costos reales, y perder el acceso a tus propias variables solo por imprimirlas haría de Rust un lenguaje insoportable. La solución es una referencia: escribir &s1 deja que una función tome prestado el acceso al valor que posee s1 sin tomar su propiedad en absoluto.
Una referencia se escribe &T para un préstamo inmutable, de solo lectura. Una firma de función como fn calculate_length(s: &String) -> usize deja claro su contrato: recibe acceso a un String que no posee, así que puede leerlo — llamar a .len(), recorrer sus caracteres — pero no puede modificarlo y, algo crucial, no lo libera (drop) cuando la función termina, porque nunca fue su dueña. Después de que la llamada retorna, la variable original en quien la llamó sigue siendo exactamente tan válida y utilizable como antes, porque nunca se movió nada.
Cuando una función realmente necesita modificar datos prestados, recurres a &mut T. La variable que se presta debe estar declarada mut, y pasas &mut s en vez de &s; dentro de la función, un parámetro &mut String te permite llamar directamente métodos que modifican, como .push_str(). La firma fn add_exclamation(s: &mut String) le dice a cualquiera que la llame, con solo leerla, que esta función va a cambiar el valor que le pasan — sin necesidad de leer la implementación para saberlo.
Rust exige exactamente una regla sobre cuántas referencias pueden existir al mismo tiempo, y vale la pena memorizarla porque el compilador te la va a hacer cumplir constantemente: en un momento dado, puedes tener cualquier cantidad de referencias inmutables (&T) a un valor, o exactamente una referencia mutable (&mut T) — nunca ambos tipos a la vez. No es una restricción arbitraria; es exactamente la condición que hace posible un data race en primer lugar. Un data race requiere dos o más punteros a la misma memoria donde al menos uno escribe y no hay sincronización entre ellos — al convertir exactamente esa configuración en un error de compilación, Rust transforma un error que tradicionalmente solo aparece bajo carga concurrente, de forma no determinista, en algo que ni siquiera puedes llegar a compilar.
El mismo borrow checker también se niega a dejar que una referencia sobreviva más tiempo que el dato al que apunta, lo que elimina por completo las referencias colgantes (dangling references). En C es fácil devolver un puntero a una variable local y terminar con un puntero a memoria del stack ya liberada en el momento en que la función retorna — comportamiento indefinido que puede funcionar bien en pruebas y fallar en producción. El compilador de Rust rastrea cuánto tiempo se le permite seguir siendo válida a cada referencia (su "lifetime") y rechaza cualquier función que dejara escapar una referencia más allá del scope del dato del que la tomó prestada — vas a conocer la sintaxis explícita de lifetimes más adelante, pero la garantía de fondo ya te está protegiendo aquí, en silencio, en cada préstamo que escribes.
Vas a encontrarte con esta regla de forma muy concreta como un error del compilador la primera vez que intentes mezclar un préstamo mutable y uno inmutable mientras ambos siguen "en uso". El Rust moderno es inteligente respecto a qué significa "en uso" — gracias a los non-lexical lifetimes, el tiempo de vida activo de un préstamo termina en su último uso real, no al final del bloque que lo contiene — así que dos referencias inmutables, r1 y r2, pueden existir, imprimirse, y después se puede crear una nueva referencia mutable r3 en ese mismo scope, porque r1 y r2 ya no se leen después de ese punto. Pero si intentas crear r3 mientras r1 o r2 todavía van a usarse más adelante, obtienes error[E0502]: cannot borrow s as mutable because it is also borrowed as immutable — y la solución casi siempre es terminar de usar los préstamos existentes antes de iniciar uno nuevo que entre en conflicto, o reducir los préstamos a scopes más pequeños que no se solapen.
En conjunto, las referencias son la forma en que el Rust idiomático mueve datos de un lado a otro sin moverlos ni clonarlos constantemente, y la firma de una función se convierte en un contrato completo y honesto: &T significa "voy a leer esto", &mut T significa "voy a cambiar esto", y un T simple significa "me quedo con esto que me diste". Una vez que ese contrato es visible en cada firma que escribes y lees, una enorme cantidad de cosas que en otros lenguajes requerían documentación cuidadosa pasan a ser algo que el compilador simplemente verifica por ti.
fn calculate_length(s: &String) -> usize {s.len()}fn main() {let s1 = String::from("hello");let len = calculate_length(&s1);println!("{} has length {}.", s1, len);}
calculate_length borrows s1 through an immutable reference instead of taking ownership, so s1 is still perfectly valid and usable in the println! after the function call returns.
fn add_exclamation(s: &mut String) {s.push_str("!");}fn main() {let mut greeting = String::from("Hello");add_exclamation(&mut greeting);println!("{}", greeting);}
Passing &mut greeting lets add_exclamation modify the caller's String in place through the reference, which is only possible because greeting itself was declared mut.
fn main() {let mut s = String::from("hello");let r1 = &s;let r2 = &s;println!("{} and {}", r1, r2);// The following would NOT compile if placed here, because r1 and r2// are still considered active through the println! call above:// let r3 = &mut s;// println!("{}", r3);// error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable// Fix: since r1 and r2 are not used again after the line above, Rust's// borrow checker (using non-lexical lifetimes) allows a new mutable// borrow to start here:let r3 = &mut s;r3.push_str(", world");println!("{}", r3);}
Two immutable references coexist safely and are printed, and thanks to non-lexical lifetimes a new mutable reference is allowed right after because r1 and r2 are never read again — the commented-out block shows the E0502 error you'd get if you tried to create that mutable reference any earlier, while the immutable ones were still going to be used.
🧠 Comprueba tu comprensión
0/1 · 0/1 answered1. Why does Rust forbid holding a mutable reference and one or more immutable references to the same value at the same time?