Referencias mutables &mut T: modificar sin poseer
La referencia mutable `&mut T` permite escribir a través de un préstamo. Por qué solo puede existir UNA a la vez, qué es el reborrow y cómo la exclusividad convierte la mutación en algo seguro.
La referencia compartida solo mira. Cuando una función necesita cambiar un valor sin apropiárselo, Rust ofrece la referencia mutable &mut T: un préstamo que concede acceso de lectura y escritura al dato del dueño. Pero viene con una condición inflexible que es el corazón de toda la seguridad del lenguaje: mientras esa referencia mutable vive, es la única vía de acceso al valor. Exclusividad total, verificada en compilación.
- Crear un préstamo mutable con
&muty escribir a través de él con*. - Entender que el dueño debe ser
mutpara poder prestarse mutablemente. - Enunciar la regla de exclusividad: como mucho una
&mut Ta la vez. - Reconocer el reborrow y por qué
&mut Tno esCopy.
Modificar a través de una referencia
Para mutar un valor prestado, la función pide &mut T y el llamador lo cede con &mut valor. El dato no cambia de dueño: se modifica en el sitio, y al volver de la llamada el dueño conserva el valor ya alterado.
fn agranda(s: &mut String) {
s.push_str(" ampliado"); // muta en el sitio a través del préstamo
}
fn main() {
let mut texto = String::from("informe");
agranda(&mut texto); // préstamo mutable temporal
println!("{texto}"); // "informe ampliado": seguimos siendo dueños
}
Como con &, el operador punto autodereferencia: s.push_str(...) es azúcar de (*s).push_str(...). Cuando el dato es un valor simple, en cambio, escribes el * a mano para asignarle:
fn incrementa(r: &mut i32) {
*r += 1; // `*r` es el lugar; escribir ahí modifica el `i32` del dueño
}
fn main() {
let mut n = 41;
incrementa(&mut n); // requiere que `n` sea `mut`
assert_eq!(n, 42);
}
La biblioteca estándar explota esta capacidad para operaciones que en otros lenguajes exigen variables temporales. std::mem::swap intercambia dos valores a través de sendas referencias mutables, y std::mem::replace sustituye el contenido devolviéndote el anterior, todo sin mover la propiedad de ningún binding:
use std::mem;
fn main() {
let mut a = String::from("izquierda");
let mut b = String::from("derecha");
mem::swap(&mut a, &mut b); // intercambia sin clonar ni mover los bindings
assert_eq!(a, "derecha");
let mut activo = String::from("v1");
let anterior = mem::replace(&mut activo, String::from("v2")); // deja "v2", devuelve "v1"
assert_eq!(anterior, "v1");
assert_eq!(activo, "v2");
}
&T · compartida
Muchas a la vez. Solo lectura. Es Copy: se duplica libremente. Nadie muta, nadie es dueño.
&mut T · exclusiva
Una sola a la vez. Lectura y escritura. No es Copy. Mientras vive, es el único acceso al valor.
El dueño debe ser mut
No puedes prestar mutablemente lo que no es mutable. Para escribir &mut x, la variable x tiene que declararse mut. Es coherente con el nivel 3: la mutabilidad es una propiedad del binding, y un préstamo mutable no puede conceder un poder que el dueño no tiene.
let inmutable = String::from("fija");
// let r = &mut inmutable; // ERROR E0596: no puedes prestar `inmutable` como mutable
let mut variable = String::from("cambiable");
let r = &mut variable; // OK: el dueño es `mut`
r.push('!');
Una sola mutable a la vez
Aquí está la regla que lo define todo. Mientras exista una referencia mutable a un valor, no puede existir ninguna otra referencia a ese valor —ni otra mutable, ni una compartida—. El préstamo mutable es exclusivo: es, temporalmente, el único camino hacia el dato.
let mut v = vec![1, 2, 3];
let a = &mut v;
// let b = &mut v; // ERROR E0499: no puede haber dos `&mut` al mismo valor
a.push(4); // mientras `a` viva, es el único acceso a `v`
println!("{v:?}"); // [1, 2, 3, 4]
El compilador reporta esto con el error E0499 (“cannot borrow v as mutable more than once at a time”) si intentas dos mutables, o E0502 si mezclas una mutable con una compartida. No es una limitación caprichosa: es la garantía que hace segura la mutación, como veremos ahora.
Por qué la exclusividad: aliasing más mutación es el desastre
Aliasing (dos referencias al mismo dato) es inofensivo mientras nadie escriba. Mutación es inofensiva mientras nadie más mire. El desastre nace de combinar ambos: mutar un dato que otro está observando. Ese es el patrón de bug que la exclusividad de &mut elimina de raíz.
let mut v = vec![1, 2, 3];
let primero = &v[0]; // préstamo compartido de un elemento
// v.push(4); // ERROR E0502: `push` necesita `&mut`, pero `primero` sigue vivo
println!("{primero}");
Si Rust permitiera ese push, el Vec podría reasignar su buffer para crecer, dejando a primero apuntando a memoria ya liberada: una referencia colgante y un use-after-free. En C++ esto es la clásica invalidación de iteradores, un bug silencioso que corrompe memoria en ejecución. Rust lo convierte en un error de compilación: la referencia compartida primero bloquea cualquier préstamo mutable de v mientras siga viva.
Casi todos los bugs de memoria y de concurrencia comparten un ADN: alguien muta un dato mientras otro lo lee o lo muta, sin coordinación. Invalidación de iteradores, use-after-free por realojo, data races entre hilos, corrupción por reentrada: variaciones del mismo pecado. La regla de exclusividad de &mut ataca la causa común en lugar de los síntomas. Si el escritor es siempre el único con acceso, no hay lector traicionado ni segundo escritor con quien competir. Por eso una sola regla —una mutable, y sola— cierra de golpe una familia entera de vulnerabilidades.
flowchart TB o[Valor declarado mut] -->|un unico prestamo| m[Referencia mutable exclusiva] m -->|concede| w[Leer y escribir en el sitio] o -->|mientras m viva| bloq[Ningun otro acceso al valor]
Reborrow: la mutable no es Copy
A diferencia de &T, la referencia mutable no implementa Copy —duplicar un &mut T produciría dos accesos exclusivos al mismo dato, una contradicción—. Entonces, ¿cómo pasas la misma &mut a dos funciones seguidas sin perderla? Gracias al reborrow: el compilador presta a través de la referencia en vez de moverla.
fn incrementa(r: &mut i32) { *r += 1; }
fn main() {
let mut n = 10;
let r = &mut n;
incrementa(r); // REBORROW implícito: se presta `&mut *r`, no se mueve `r`
incrementa(r); // `r` sigue usable porque el reborrow anterior ya terminó
println!("{n}"); // 12
}
En cada llamada, el compilador inserta un préstamo temporal &mut *r que dura solo lo que dura la función; al volver, r recupera su exclusividad y puede represtarse. El reborrow es lo que hace ergonómicas las referencias mutables sin romper la exclusividad: nunca hay dos accesos mutables simultáneos, solo uno detrás de otro.
Si pasas r a una función que toma &mut i32 por valor y luego lo consumes, técnicamente moverías r; pero en la enorme mayoría de los casos el compilador aplica un reborrow automático y r sobrevive. La distinción importa cuando guardas la referencia en un struct o la devuelves: ahí puede ocurrir un move real. La heurística práctica: pasar una &mut a una función y seguir usándola después casi siempre funciona, porque el reborrow trabaja por ti.
El nombre &mut es un pequeño accidente histórico que confunde a todos al empezar. No lo leas como “referencia mutable”, léelo como referencia exclusiva. La mutación es una consecuencia, no la esencia: se te permite escribir porque eres el único con acceso, y no al revés. Esta relectura reorganiza todo lo que sabes. La regla “una sola &mut” ya no es una restricción sobre la escritura, sino la definición de exclusividad: mientras tú la tengas, nadie más existe para ese dato. De ahí se deduce por qué &mut T no es Copy (dos copias serían dos exclusivos, absurdo), por qué bloquea también a los lectores compartidos (un lector rompería tu exclusividad), y por qué habilita optimizaciones que C no puede hacer sin anotaciones: el compilador sabe, sin analizar nada, que a través de un &mut nadie más va a tocar la memoria, así que puede mantener valores en registros con la certeza de que no hay alias oculto. La palabra mut engaña; el concepto es exclusividad, y la exclusividad es lo que vuelve la mutación tan segura como la lectura.
&mut T es un préstamo exclusivo: mientras vive, es la única vía de acceso al valor, y por eso puede mutar con seguridad. Solo puede existir una a la vez, exige que el dueño sea mut y no es Copy. El reborrow —el préstamo temporal &mut *r que el compilador inserta solo— es lo que te deja reutilizarla en varias llamadas sin moverla.
- Escribe
fn duplica(v: &mut Vec<i32>)que multiplique cada elemento por dos con un buclefor x in v.iter_mut(), y compruébalo sobre unVecmutable. - Intenta crear dos
&mutal mismo valor en el mismo scope y lee el error E0499 entero. - Toma un
&elementocompartido de unVece intenta hacerpushmientras sigue vivo: identifica en el E0502 quién bloquea a quién. - Pasa una misma
&mut i32a dos funciones seguidas y explica por qué compila pese a que&mut Tno esCopy. - Reescribe mentalmente la regla “una sola
&mut” usando la palabra “exclusiva” en vez de “mutable”.