Borrowing: Referencias Seguras en Rust
El borrowing (préstamo) permite usar valores sin tomar su ownership. Las referencias nos permiten referir a un valor sin ser responsables de liberarlo, resolviendo muchos problemas que surgen con el sistema de ownership.
¿Qué es Borrowing?
Borrowing es crear referencias a un valor sin tomar su ownership. Es como “pedir prestado” el valor - puedes usarlo, pero debes devolverlo intacto.
fn main () {
let s1 = String :: from ( "hola" );
// Crear una referencia (borrowing)
let longitud = calcular_longitud ( & s1);
// s1 sigue siendo válido porque no transferimos ownership
println! ( "La longitud de '{s1}' es {longitud}" );
}
fn calcular_longitud (s : & String ) -> usize {
s . len ()
} // s sale de scope, pero no elimina el valor porque no es propietario
Referencias Inmutables
Por defecto, las referencias son inmutables:
fn main () {
let s = String :: from ( "hola mundo" );
// Múltiples referencias inmutables están permitidas
let r1 = & s;
let r2 = & s;
let r3 = & s;
println! ( "r1: {r1}" );
println! ( "r2: {r2}" );
println! ( "r3: {r3}" );
// Todas las referencias pueden coexistir
println! ( "Original: {s}" );
// Pasar referencias a funciones
imprimir_string ( & s);
analizar_string (r1);
}
fn imprimir_string (texto : & String ) {
println! ( "Imprimiendo: {texto}" );
}
fn analizar_string (texto : & String ) {
println! ( "Longitud: {}" , texto . len ());
println! ( "¿Está vacío? {}" , texto . is_empty ());
// texto.push_str("!!!"); // ERROR: no se puede modificar
}
Referencias Mutables
Para modificar un valor a través de una referencia, necesitas una referencia mutable:
fn main () {
let mut s = String :: from ( "hola" );
// Crear referencia mutable
cambiar_string ( &mut s);
println! ( "Después del cambio: {s}" );
}
fn cambiar_string (texto : &mut String ) {
texto . push_str ( ", mundo!" );
}
Restricciones de Referencias Mutables
Solo puede haber una referencia mutable a un valor en un scope dado:
fn main () {
let mut s = String :: from ( "hola" );
let r1 = &mut s;
// let r2 = &mut s; // ERROR: cannot borrow `s` as mutable more than once
println! ( "{r1}" );
// r1 ya no se usa después de esto, así que podemos crear otra
let r2 = &mut s;
println! ( "{r2}" );
}
Reglas de Borrowing
La Regla Fundamental
No puedes tener referencias mutables e inmutables al mismo tiempo:
fn main () {
let mut s = String :: from ( "hola" );
let r1 = & s; // OK - referencia inmutable
let r2 = & s; // OK - otra referencia inmutable
// let r3 = &mut s; // ERROR: cannot borrow as mutable while borrowed as immutable
println! ( "{r1} y {r2}" );
// r1 y r2 ya no se usan después de este punto
let r3 = &mut s; // OK - ahora podemos tener una referencia mutable
println! ( "{r3}" );
}
Lifetime de Referencias
Las referencias tienen un “lifetime” - deben ser válidas mientras se usan:
fn main () {
let mut s = String :: from ( "hola" );
{
let r = & s; // OK - r vive hasta el final de este bloque
println! ( "{r}" );
} // r sale de scope aquí
// Ahora podemos crear una referencia mutable
let r_mut = &mut s;
r_mut . push_str ( " mundo" );
println! ( "{r_mut}" );
}
Tipos de Referencias
Referencias a Diferentes Tipos
fn main () {
// Referencias a tipos primitivos
let numero = 42 ;
let ref_numero = & numero;
println! ( "Número: {numero}, Referencia: {ref_numero}" );
// Referencias a arrays
let array = [ 1 , 2 , 3 , 4 , 5 ];
let ref_array = & array;
println! ( "Primer elemento: {}" , ref_array[ 0 ]);
// Referencias a tuplas
let tupla = ( 10 , "hola" );
let ref_tupla = & tupla;
println! ( "Segundo elemento: {}" , ref_tupla . 1 );
}
Referencias vs Punteros
fn main () {
let x = 5 ;
let ref_x = & x;
// Dereferencing con *
println! ( "Valor de x: {x}" );
println! ( "Valor a través de referencia: {}" , * ref_x);
println! ( "Referencia (automática): {ref_x}" ); // Rust desreferencia automáticamente
// Con strings
let s = String :: from ( "hola" );
let ref_s = & s;
println! ( "Longitud directa: {}" , s . len ());
println! ( "Longitud por referencia: {}" , ref_s . len ()); // Desreferenciado automático
println! ( "Longitud explícita: {}" , ( * ref_s) . len ()); // Desreferenciado manual
}
Slices: Referencias a Porciones
Los slices son referencias a una porción contigua de una colección:
String Slices
fn main () {
let s = String :: from ( "hola mundo" );
// Crear slices
let hola = & s[ 0 .. 4 ]; // "hola"
let mundo = & s[ 5 .. 10 ]; // "mundo"
let completo = & s[ .. ]; // "hola mundo"
let desde_inicio = & s[ .. 5 ]; // "hola "
let hasta_final = & s[ 5 .. ]; // "mundo"
println! ( "Original: {s}" );
println! ( "Hola: {hola}" );
println! ( "Mundo: {mundo}" );
println! ( "Completo: {completo}" );
// Función que retorna un slice
let primera_palabra = obtener_primera_palabra ( & s);
println! ( "Primera palabra: {primera_palabra}" );
}
fn obtener_primera_palabra (s : & String ) -> & str {
let bytes = s . as_bytes ();
for (i, & item) in bytes . iter () . enumerate () {
if item == b' ' {
return & s[ 0 .. i];
}
}
& s[ .. ] // Si no hay espacios, retorna toda la string
}
Mejorando con &str
fn main () {
let s = String :: from ( "hola mundo" );
// Esta función funciona con String y &str
let palabra1 = primera_palabra ( & s);
let palabra2 = primera_palabra ( "hola rust" );
println! ( "Palabra 1: {palabra1}" );
println! ( "Palabra 2: {palabra2}" );
}
// Mejor: usar &str en lugar de &String
fn primera_palabra (s : & str ) -> & str {
let bytes = s . as_bytes ();
for (i, & item) in bytes . iter () . enumerate () {
if item == b' ' {
return & s[ 0 .. i];
}
}
s
}
Array Slices
fn main () {
let array = [ 1 , 2 , 3 , 4 , 5 ];
// Diferentes slices del array
let slice_completo = & array;
let slice_parcial = & array[ 1 .. 4 ]; // [2, 3, 4]
let primeros_tres = & array[ .. 3 ]; // [1, 2, 3]
println! ( "Array completo: {:?}" , slice_completo);
println! ( "Slice parcial: {:?}" , slice_parcial);
println! ( "Primeros tres: {:?}" , primeros_tres);
// Pasar slices a funciones
imprimir_slice (slice_parcial);
let suma = sumar_slice ( & array[ 2 .. ]);
println! ( "Suma desde índice 2: {suma}" );
}
fn imprimir_slice (slice : & [ i32 ]) {
println! ( "Imprimiendo slice: {:?}" , slice);
}
fn sumar_slice (slice : & [ i32 ]) -> i32 {
slice . iter () . sum ()
}
Patrones Comunes con Borrowing
Funciones que Solo Leen
fn main () {
let texto = String :: from ( "Rust es genial" );
let numeros = vec! [ 1 , 2 , 3 , 4 , 5 ];
// Funciones que solo leen no necesitan ownership
mostrar_info ( & texto);
estadisticas ( & numeros);
// Los valores originales siguen disponibles
println! ( "Texto original: {texto}" );
println! ( "Números originales: {:?}" , numeros);
}
fn mostrar_info (s : & String ) {
println! ( "Texto: '{s}'" );
println! ( "Longitud: {}" , s . len ());
println! ( "¿Contiene 'Rust'? {}" , s . contains ( "Rust" ));
}
fn estadisticas (nums : & Vec < i32 >) {
println! ( "Números: {:?}" , nums);
println! ( "Cantidad: {}" , nums . len ());
println! ( "Suma: {}" , nums . iter () . sum :: < i32 >());
println! ( "Promedio: {:.2}" , nums . iter () . sum :: < i32 >() as f64 / nums . len () as f64 );
}
Funciones que Modifican
fn main () {
let mut mensaje = String :: from ( "Hola" );
let mut puntuaciones = vec! [ 85 , 90 , 78 ];
// Modificar a través de referencias mutables
agregar_exclamacion ( &mut mensaje);
agregar_bonus ( &mut puntuaciones, 5 );
println! ( "Mensaje modificado: {mensaje}" );
println! ( "Puntuaciones con bonus: {:?}" , puntuaciones);
}
fn agregar_exclamacion (s : &mut String ) {
s . push ( '!' );
}
fn agregar_bonus (puntuaciones : &mut Vec < i32 >, bonus : i32 ) {
for puntuacion in puntuaciones . iter_mut () {
* puntuacion += bonus;
}
}
Builder Pattern con References
struct Configuracion {
nombre : String ,
debug : bool ,
max_conexiones : u32 ,
}
impl Configuracion {
fn nueva () -> Self {
Configuracion {
nombre : String :: from ( "default" ),
debug : false ,
max_conexiones : 100 ,
}
}
// Métodos que toman &mut self para modificar
fn set_nombre ( &mut self , nombre : & str ) -> &mut Self {
self . nombre = nombre . to_string ();
self
}
fn set_debug ( &mut self , debug : bool ) -> &mut Self {
self . debug = debug;
self
}
fn set_max_conexiones ( &mut self , max : u32 ) -> &mut Self {
self . max_conexiones = max;
self
}
// Método que toma &self para leer
fn mostrar ( & self ) {
println! ( "Configuración:" );
println! ( " Nombre: {}" , self . nombre);
println! ( " Debug: {}" , self . debug);
println! ( " Max conexiones: {}" , self . max_conexiones);
}
}
fn main () {
let mut config = Configuracion :: nueva ();
// Chaining methods que usan referencias mutables
config
. set_nombre ( "MiApp" )
. set_debug ( true )
. set_max_conexiones ( 200 );
config . mostrar ();
}
Ejercicios Prácticos
Ejercicio 1: Analizador de Texto
fn main () {
let texto = String :: from ( "Rust es un lenguaje de programación rápido y seguro" );
// Análisis usando referencias
let info = analizar_texto ( & texto);
println! ( "Texto: '{texto}'" );
println! ( "Palabras: {}" , info . 0 );
println! ( "Caracteres: {}" , info . 1 );
println! ( "Palabra más larga: '{}'" , info . 2 );
// Modificar el texto
let mut texto_mut = texto;
agregar_emoji ( &mut texto_mut);
println! ( "Con emoji: {texto_mut}" );
}
fn analizar_texto (texto : & str ) -> ( usize , usize , & str ) {
let palabras : Vec < & str > = texto . split_whitespace () . collect ();
let num_palabras = palabras . len ();
let num_chars = texto . chars () . count ();
let palabra_mas_larga = palabras
. iter ()
. max_by_key ( | palabra | palabra . len ())
. unwrap ();
(num_palabras, num_chars, palabra_mas_larga)
}
fn agregar_emoji (texto : &mut String ) {
texto . push_str ( " 🦀" );
}
Ejercicio 2: Manipulador de Lista
fn main () {
let mut numeros = vec! [ 3 , 1 , 4 , 1 , 5 , 9 , 2 , 6 ];
println! ( "Lista original: {:?}" , numeros);
// Operaciones que no modifican
mostrar_estadisticas ( & numeros);
let pares = obtener_pares ( & numeros);
println! ( "Números pares: {:?}" , pares);
// Operaciones que modifican
duplicar_valores ( &mut numeros);
println! ( "Después de duplicar: {:?}" , numeros);
filtrar_mayores_que ( &mut numeros, 10 );
println! ( "Mayores que 10: {:?}" , numeros);
}
fn mostrar_estadisticas (lista : & Vec < i32 >) {
let suma : i32 = lista . iter () . sum ();
let promedio = suma as f64 / lista . len () as f64 ;
let max = lista . iter () . max () . unwrap ();
let min = lista . iter () . min () . unwrap ();
println! ( "Estadísticas:" );
println! ( " Suma: {suma}" );
println! ( " Promedio: {promedio:.2}" );
println! ( " Máximo: {max}" );
println! ( " Mínimo: {min}" );
}
fn obtener_pares (lista : & Vec < i32 >) -> Vec < i32 > {
lista . iter ()
. filter ( |&& x | x % 2 == 0 )
. cloned ()
. collect ()
}
fn duplicar_valores (lista : &mut Vec < i32 >) {
for valor in lista . iter_mut () {
* valor *= 2 ;
}
}
fn filtrar_mayores_que (lista : &mut Vec < i32 >, limite : i32 ) {
lista . retain ( |& x | x > limite);
}
Ejercicio 3: Parser de Configuración Simple
fn main () {
let config_text = "nombre=MiApp \n debug=true \n port=8080 \n max_users=1000" ;
let config = parsear_config (config_text);
mostrar_config ( & config);
// Modificar configuración
let mut config_mut = config;
actualizar_puerto ( &mut config_mut, 3000 );
mostrar_config ( & config_mut);
}
#[derive( Debug )]
struct Config {
nombre : String ,
debug : bool ,
port : u32 ,
max_users : u32 ,
}
fn parsear_config (texto : & str ) -> Config {
let mut nombre = String :: new ();
let mut debug = false ;
let mut port = 8080 ;
let mut max_users = 100 ;
for linea in texto . lines () {
if let Some ((clave, valor)) = parsear_linea (linea) {
match clave {
"nombre" => nombre = valor . to_string (),
"debug" => debug = valor == "true" ,
"port" => port = valor . parse () . unwrap_or ( 8080 ),
"max_users" => max_users = valor . parse () . unwrap_or ( 100 ),
_ => println! ( "Clave desconocida: {clave}" ),
}
}
}
Config { nombre, debug, port, max_users }
}
fn parsear_linea (linea : & str ) -> Option <( & str , & str )> {
let partes : Vec < & str > = linea . split ( '=' ) . collect ();
if partes . len () == 2 {
Some ((partes[ 0 ], partes[ 1 ]))
} else {
None
}
}
fn mostrar_config (config : & Config ) {
println! ( "Configuración:" );
println! ( " Nombre: {}" , config . nombre);
println! ( " Debug: {}" , config . debug);
println! ( " Puerto: {}" , config . port);
println! ( " Max usuarios: {}" , config . max_users);
}
fn actualizar_puerto (config : &mut Config , nuevo_puerto : u32 ) {
config . port = nuevo_puerto;
println! ( "Puerto actualizado a: {nuevo_puerto}" );
}
Errores Comunes con Borrowing
Error 1: Referencias Colgantes (Dangling References)
// ¡ESTO NO COMPILA!
// fn crear_referencia_colgante() -> &String {
// let s = String::from("hola");
// &s // ERROR: returns a reference to data owned by the current function
// }
// Solución: retornar el valor, no una referencia
fn crear_string_correcta () -> String {
String :: from ( "hola" )
}
fn main () {
let s = crear_string_correcta ();
println! ( "{s}" );
}
Error 2: Múltiples Referencias Mutables
fn main () {
let mut s = String :: from ( "hola" );
// ¡ESTO NO COMPILA!
// let r1 = &mut s;
// let r2 = &mut s;
// println!("{r1} {r2}");
// Solución: usar las referencias en diferentes scopes
{
let r1 = &mut s;
r1 . push_str ( " mundo" );
} // r1 sale de scope aquí
let r2 = &mut s; // Ahora está bien
r2 . push ( '!' );
println! ( "{r2}" );
}
Puntos Clave para Recordar
Las referencias no tienen ownership del valor que referencian
& crea referencias inmutables , &mut crea referencias mutables
Solo una referencia mutable OR múltiples inmutables a la vez
Las referencias deben ser válidas durante su uso (no dangling)
Los slices son referencias a porciones de colecciones
&str es más flexible que &String para parámetros
Rust desreferencia automáticamente en muchos contextos
Las reglas de borrowing previenen data races y use-after-free
Anterior
Ownership
Siguiente
Structs