Borrowing en Rust
En la página de ownership viste un problema: si le pasas un String a una función, el valor se mueve, y después ya no puedes usarlo. Andar moviendo valores todo el tiempo (o clonándolos) sería muy incómodo. El borrowing (préstamo) es la solución de Rust.
Hacer borrowing significa dejar que otra parte del código use un valor sin quedarse con él. Es como prestarle un libro a un amigo: lo lee, te lo devuelve y el libro sigue siendo tuyo.
Referencias: tomar prestado con &
Sección titulada «Referencias: tomar prestado con &»Creas una referencia poniendo & delante de un valor. Una referencia le permite a una función ver los datos sin ser su dueña:
fn print_length(s: &String) { // toma prestado s, no es su dueña println!("Longitud: {}", s.len());}
fn main() { let text = String::from("hello"); print_length(&text); // le prestamos text a la función println!("Sigue siendo mío: {text}"); // ✅ text sigue siendo válida aquí}Salida:
Longitud: 5Sigue siendo mío: hello&text quiere decir “deja que esta función tome prestado text”. Como la función solo lo tomó prestado, main sigue siendo la dueña y puedes seguir usando text después. Compáralo con la página de ownership: allí, el mismo código sin & daba error.
Referencias mutables: tomar prestado para modificar
Sección titulada «Referencias mutables: tomar prestado para modificar»Una referencia & normal es de solo lectura: puedes mirar, pero no tocar. Si una función necesita cambiar el valor prestado, usa una referencia mutable con &mut:
fn add_excitement(s: &mut String) { s.push_str("!!!");}
fn main() { let mut text = String::from("hola"); add_excitement(&mut text); // lo prestamos de forma mutable println!("{text}"); // hola!!!}Salida:
hola!!!Fíjate en que tres cosas tienen que encajar: text se declara con mut, la función pide un &mut String y la llamada pasa &mut text. Las tres son necesarias para modificar un valor prestado.
Las reglas del borrowing
Sección titulada «Las reglas del borrowing»Rust aplica dos reglas a las referencias. Existen para evitar las carreras de datos (data races): un tipo de error en el que dos partes del código tocan los mismos datos al mismo tiempo y los corrompen.
En cualquier momento puedes tener o bien:
- cualquier cantidad de referencias de solo lectura (
&), o bien - exactamente una referencia mutable (
&mut),
pero nunca las dos cosas a la vez.
Dicho de forma sencilla: muchos lectores o un solo escritor, pero nunca ambos juntos. Este código rompe la regla:
fn main() { let mut text = String::from("hola");
let r1 = &text; // un préstamo de solo lectura let r2 = &mut text; // ❌ no se puede prestar de forma mutable mientras r1 exista
println!("{r1} {r2}");}El compilador te detiene:
error[E0502]: cannot borrow `text` as mutable because it is also borrowed as immutableEl mensaje dice que no puedes tomar prestado text como mutable porque ya está prestado como inmutable.
¿Cuándo termina un préstamo?
Sección titulada «¿Cuándo termina un préstamo?»Quizás esperes que un préstamo dure hasta la } que cierra su ámbito. No es así. Un préstamo dura solo hasta el último lugar donde se usa la referencia. A partir de ahí, el valor se puede volver a prestar, incluso de forma mutable.
Eso significa que el ejemplo roto de arriba se arregla simplemente terminando con r1 antes de crear r2:
fn main() { let mut text = String::from("hola");
let r1 = &text; println!("Leyendo: {r1}"); // último uso de r1, así que su préstamo termina aquí
let r2 = &mut text; // ✅ bien: ya nadie está leyendo r2.push_str(", mundo");
println!("{text}");}Salida:
Leyendo: holahola, mundoLa regla sigue siendo “muchos lectores o un solo escritor”. Lo que pasa es que se comprueba sobre el tramo de código en el que cada referencia se usa de verdad. El lector r1 ya ha terminado cuando aparece el escritor r2, y r2 ha terminado antes de que el último println! vuelva a leer text. Nada se solapa, así que Rust está contento.
Esta es la forma más habitual de arreglar un error de borrowing: cambiar el orden. Haz primero todas las lecturas que necesites y después las escrituras (o al revés), en lugar de mezclarlas.
Por qué esto importa
Sección titulada «Por qué esto importa»Estas reglas se comprueban por completo al compilar, así que no cuestan nada mientras el programa se ejecuta. Así es como Rust puede prometer “sin carreras de datos” y a la vez ser tan rápido como C: la seguridad queda demostrada antes de que el programa arranque.
Práctica
Sección titulada «Práctica»Intenta resolver cada ejercicio por tu cuenta primero, en el Playground de Rust o en un proyecto local. Si el compilador se queja, lee el error: casi siempre muestra exactamente dónde se solapan los préstamos.
1. Contar sin quedárselo
Sección titulada «1. Contar sin quedárselo»Escribe una función total_letters que tome prestado un Vec<String> y devuelva cuántas letras tienen todas las palabras juntas. Después de llamarla, main todavía tiene que poder imprimir el vector.
Pista: el tipo del parámetro es &Vec<String>, y la llamas con &words. Recorre el vector prestado con for word in list y suma word.len().
Ver solución
fn total_letters(list: &Vec<String>) -> usize { let mut total = 0; for word in list { total += word.len(); } total}
fn main() { let words = vec![String::from("borrow"), String::from("check")]; let count = total_letters(&words); // prestamos el vector println!("{count} letras"); println!("{:?} sigue siendo de main", words);}Salida:
11 letras["borrow", "check"] sigue siendo de mainLa función solo necesita mirar las palabras, así que basta con un préstamo & de solo lectura. Si escribieras list: Vec<String>, el vector se movería a la función y el último println! no compilaría.
Las palabras se quedan en inglés a propósito: .len() cuenta bytes, no letras, y una letra con tilde ocupa dos bytes, así que con otras palabras el resultado podría no coincidir con las letras que ves.
2. Una lista de la compra
Sección titulada «2. Una lista de la compra»Escribe dos funciones: add_item, que añade un artículo a una lista de la compra, y print_list, que imprime todos los artículos. Añade tres artículos y luego imprime la lista.
Pista: una función cambia la lista y la otra solo la lee. ¿Cuál necesita &mut Vec<String> y a cuál le basta con &Vec<String>? Para convertir un &str en un String, usa String::from(item).
Ver solución
fn add_item(list: &mut Vec<String>, item: &str) { list.push(String::from(item));}
fn print_list(list: &Vec<String>) { for item in list { println!("- {item}"); }}
fn main() { let mut list = Vec::new(); add_item(&mut list, "leche"); add_item(&mut list, "pan"); add_item(&mut list, "manzanas"); print_list(&list);}Salida:
- leche- pan- manzanasadd_item cambia la lista, así que recibe un préstamo &mut, y list tiene que ser mut en main. print_list solo lee, así que le basta con un & normal. Cada préstamo &mut list termina cuando su llamada vuelve, y por eso la siguiente llamada puede volver a tomarlo prestado.
3. Dos escritores a la vez
Sección titulada «3. Dos escritores a la vez»Este programa no compila. Arréglalo para que imprima Puntuación: 10 puntos, manteniendo tanto first como second.
fn main() { let mut message = String::from("Puntuación:"); let first = &mut message; let second = &mut message; first.push_str(" 10"); second.push_str(" puntos"); println!("{message}");}error[E0499]: cannot borrow `message` as mutable more than once at a time --> src/main.rs:4:18 |3 | let first = &mut message; | ------------ first mutable borrow occurs here4 | let second = &mut message; | ^^^^^^^^^^^^ second mutable borrow occurs here5 | first.push_str(" 10"); | ----- first borrow later used hereEl compilador dice que no puedes tomar prestado message como mutable más de una vez al mismo tiempo: el primer préstamo mutable ocurre en la línea 3, el segundo en la línea 4, y el primero se vuelve a usar después, en la línea 5.
Pista: solo un escritor a la vez. Mira la sección ¿Cuándo termina un préstamo? de arriba: ¿puedes terminar de usar first antes de crear second?
Ver solución
fn main() { let mut message = String::from("Puntuación:");
let first = &mut message; first.push_str(" 10"); // último uso de first: su préstamo termina aquí
let second = &mut message; second.push_str(" puntos");
println!("{message}");}Salida:
Puntuación: 10 puntosEn el original, first todavía se iba a usar (en la línea 5) cuando se creó second en la línea 4. Eso son dos escritores al mismo tiempo. Al subir first.push_str, first termina antes de que exista second, así que nunca hay más de un &mut a la vez.
Resumen
Sección titulada «Resumen»- El borrowing (
&) permite que el código use un valor sin quedarse con él: te lo devuelven. - Una referencia
&normal es de solo lectura; usa&mutpara modificar el valor prestado. - Para modificar algo a través de una referencia, la variable, el parámetro y la llamada necesitan
mut/&mut. - La regla: muchos lectores o un solo escritor, nunca ambos a la vez.
- Un préstamo termina en el último uso de la referencia, así que reordenar el código suele arreglar un error de borrowing.
- Todo esto se comprueba al compilar, así que no cuesta nada en tiempo de ejecución.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. ¿Qué hace poner & delante de un valor al pasárselo a una función?
2. Según las reglas del borrowing, ¿qué combinación está permitida al mismo tiempo?
3. ¿Qué necesitas para modificar un valor a través de una referencia?
4. ¿Por qué Rust puede aplicar las reglas del borrowing sin ningún costo de rendimiento al ejecutar el programa?
Puntuación: 0 / 4
Para leer más
Sección titulada «Para leer más»- Referencias y borrowing — El libro de Rust (en inglés): el capítulo oficial, que también explica las referencias colgantes (dangling references) y por qué Rust se niega a crearlas.
- Borrowing — Rust by Example (en inglés): ejemplos ejecutables de préstamos con
&, con páginas de continuación sobre préstamos mutables y aliasing. - Non-lexical lifetimes — The Edition Guide (en inglés): el cambio que hizo que los préstamos terminen en su último uso y no en la llave de cierre.
- The Slice Type — El libro de Rust (en inglés): tomar prestada solo una parte de un string o de un array, el siguiente paso natural después de las referencias.
reference— documentación de la biblioteca estándar (en inglés): qué son&Ty&mut T, y qué traits implementan.