En Rust, una referencia mutable (&mut T) es una referencia que permite modificar el valor que está apuntando. A diferencia de las referencias inmutables (&T), que solo permiten la lectura, las mutables permiten la escritura, pero bajo una restricción extremadamente estricta: la exclusividad. La regla fundamental del borrow checker establece que, en cualquier punto del tiempo, puedes tener o bien una única referencia mutable a un dato, o bien múltiples referencias inmutables, pero nunca ambas simultáneamente sobre el mismo valor.
Esta restricción es la que previene las data races (condiciones de carrera de datos) en tiempo de compilación. Si Rust permitiera tener una referencia mutable y una inmutable al mismo tiempo, la referencia inmutable podría intentar leer un valor mientras la mutable lo está cambiando, resultando en datos corruptos o inconsistentes. El compilador garantiza que, si tienes un &mut T, eres el único que tiene acceso a ese valor, asegurando la integridad de la memoria sin necesidad de un recolector de basura.
Gracias a las Non-Lexical Lifetimes (NLL), el compilador no limita la vida de una referencia al final del bloque {} (el scope léxico), sino al último punto donde la referencia sea realmente utilizada. Esto permite que el código sea mucho más flexible, permitiendo reutilizar un valor tras haber terminado de usar su referencia inmutable.
// Función que recibe una referencia mutable a un i32
fn incrementar(valor: &mut i32) {
// Usamos el operador de desreferencia '*' para acceder y modificar el valor
*valor += 1;
}
// Función que recibe una referencia mutable a un String
fn añadir_prefijo(texto: &mut String, prefijo: &str) {
texto.push_str(prefijo);
}
fn main() {
let mut numero: i32 = 10;
let mut mensaje: String = String::from("Hola");
// 1. Uso de referencias mutables en funciones
incrementar(&mut numero);
println!("Número incrementado: {}", numero);
// 2. Demostración de NLL (Non-Lexical Lifetimes)
let r_inmutable: &String = &mensaje;
println!("Lectura inmutable: {}", r_inmutable);
// Aquí, la vida de 'r_inmutable' termina porque no se usa más.
// Esto permite que el borrow checker nos deje tomar una mutabilidad después.
añadir_prefijo(&mut mensaje, ", mundo!");
println!("Mensaje final: {}", mensaje);
// 3. Uso directo del operador de desreferencia '*'
let mut x: i32 = 5;
let r_mut: &mut i32 = &mut x;
*r_mut = *r_mut * 10; // Leemos con '*', operamos y escribimos con '*'
println!("X tras desreferencia: {}", x);
}
Explicación del Código
– fn incrementar(valor: &mut i32): Define una función que acepta una referencia mutable al tipo i32. Dentro, *valor utiliza el operador de desreferencia para acceder al valor entero y permitir la operación += 1.
– fn añadir_prefijo(texto: &mut String, prefijo: &str): Recibe una referencia mutable texto de tipo String para modificarla directamente mediante el método push_str.
– let mut numero: i32 = 10;: Se declara la variable numero como mut para permitir que su contenido sea prestado de forma mutable.
– let r_inmutable: &String = &mensaje;: Se crea una referencia inmutable hacia mensaje. Debido a las NLL, una vez que se ejecuta el println! que utiliza r_inmutable, esta referencia deja de ser válida para el compilador, liberando el valor para una nueva mutación.
– añadir_prefijo(&mut mensaje, ", mundo!");: Se pasa una referencia mutable a mensaje. Esto es posible solo porque r_inmutable ya no está en uso.
– let r_mut: &mut i32 = &mut x;: Se obtiene una referencia mutable al tipo i32 para la variable x.
– *r_mut = *r_mut * 10;: Se utiliza el operador * para desreferenciar r_mut. Primero se lee el valor (*r_mut), se multiplica por 10 y el resultado se asigna de vuelta a la dirección de memoria apuntada por r_mut.
N° 28