rust-variables-mutabilidad-shadowing

Variables y mutabilidad en Rust: let, mut y shadowing

  • 6 min

Una variable en Rust es un nombre asociado a un valor que, por defecto, no se puede modificar.

En Rust, declarar una variable no significa automáticamente “esto lo podré cambiar luego”. La decisión por defecto es justo la contraria: si algo no necesita cambiar, no cambia.

Esta elección puede parecer restrictiva al principio, pero tiene una intención muy clara. Hace que el código sea más predecible, más fácil de leer y menos propenso a errores raros.

En este artículo vamos a ver cómo declarar variables con let, cuándo usar mut, qué diferencia hay con const y cómo funciona una característica muy práctica de Rust llamada shadowing.

Declaración de variables con let

Para crear una variable en Rust usamos la palabra clave let.

fn main() {
    let x = 5;
    println!("El valor de x es: {}", x);
}
Copied!

Hasta aquí todo parece normal. Rust es un lenguaje de tipado estático, lo que significa que debe conocer los tipos de todas las variables en tiempo de compilación. Sin embargo, no hemos escrito let x: i32 = 5;.

Esto es gracias a la inferencia de tipos. Rust es lo suficientemente listo para deducir que, si asignamos un 5, probablemente queramos un entero (por defecto i32).

El “problema” de la inmutabilidad

Ahora, intentemos hacer lo que haríamos en cualquier otro lenguaje: cambiar el valor.

fn main() {
    let x = 5;
    println!("El valor de x es: {}", x);

    x = 6; // ¡Error!
    println!("El valor de x es: {}", x);
}
Copied!

Si intentas compilar esto (cargo run), Rust no solo te dará un error, sino que el compilador te explicará exactamente qué pasa con un mensaje muy descriptivo:

error[E0384]: cannot assign twice to immutable variable `x`
 --> src/main.rs:4:5
  |
2 |     let x = 5;
  |         - first assignment to `x`
3 |     println!("El valor de x es: {}", x);
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable
Copied!

Nos dice: “No puedes asignar dos veces a la variable inmutable x”.

¿Por qué Rust hace esto?

Tener variables inmutables por defecto hace que el código sea más fácil de razonar. Si ves let x = 5, tienes la garantía de que x será 5 en todo su ámbito.

Además, es fundamental para la seguridad en concurrencia: si un dato no cambia, varios hilos pueden leerlo a la vez sin miedo a condiciones de carrera.

Mutabilidad con mut

Aun así, un programa donde nada cambia no suele ser muy útil. A veces necesitamos modificar valores (un contador, una posición en un juego, etc.).

Para hacer una variable mutable, debemos ser explícitos y añadir la palabra clave mut delante del nombre de la variable.

fn main() {
    let mut x = 5;
    println!("El valor de x es: {}", x);

    x = 6;
    println!("El valor de x es: {}", x);
}
Copied!

Ahora el código compila y funciona perfectamente.

Al añadir mut, estamos comunicando dos cosas:

  1. Al compilador: “Permíteme reasignar esta variable”.
  2. A otros programadores (o a ti mismo en el futuro): “Ojo, esta variable va a cambiar de valor a lo largo de la función”.

Constantes (const)

Si las variables son inmutables por defecto, ¿en qué se diferencian de las constantes?

En Rust tenemos la palabra clave const, pero tiene reglas muy diferentes a un let inmutable:

  1. Requieren tipo explícito: No puedes confiar en la inferencia. Debes escribir el tipo (ej: : u32).
  2. Solo expresiones constantes: Su valor debe poder calcularse en tiempo de compilación, no en tiempo de ejecución. No puedes asignar a una constante el resultado de una función que lee un archivo, por ejemplo.
  3. Ámbito global: A diferencia de las variables, las constantes pueden declararse fuera de la función main, en el ámbito global del módulo.
  4. Inmutables para siempre: No existe const mut.

Por convención, en Rust las constantes se escriben en MAYÚSCULAS_CON_GUIONES_BAJOS (SCREAMING_SNAKE_CASE).

const MAX_PUNTOS: u32 = 100_000;
const SEGUNDOS_EN_HORA: u32 = 60 * 60; // Esto es válido, se calcula al compilar

fn main() {
    println!("El máximo de puntos es: {}", MAX_PUNTOS);
}
Copied!

Usa const para valores que son verdaderamente fijos en el universo de tu programa (Pi, límites de configuración, constantes físicas). Usa let para todo lo demás.

Shadowing

En Rust, puedes declarar una nueva variable con el mismo nombre que una anterior. La nueva variable “hace sombra” (oculta) a la anterior: el shadowing.

fn main() {
    let x = 5;

    // Aquí redeclaramos 'x'. La anterior deja de ser accesible.
    let x = x + 1;

    {
        // Shadowing dentro de un ámbito interno
        let x = x * 2;
        println!("El valor de x en el scope interno es: {}", x); // 12
    }

    println!("El valor de x en el scope externo es: {}", x); // 6
}
Copied!

Fíjate que usamos let cada vez.