Mutabilidad (cambio)

Puedes ver este capítulo en YouTube en inglés

Cuando se declara una variable con `let, es inmutable (no se puede cambiar su valor).

El código siguiente no funciona.

fn main() {
    let mi_numero = 8;
    mi_numero = 10; // ⚠️
}

El compilador indica error[E0384]: cannot assign twice to immutable variable mi_numero.

error[E0384]: cannot assign twice to immutable variable `mi_numero`
 --> src/main.rs:3:5
  |
2 |     let mi_numero = 8;
  |         ---------
  |         |
  |         first assignment to `mi_numero`
  |         help: consider making this binding mutable: `mut mi_numero`
3 |     mi_numero = 10; // ⚠️
  |     ^^^^^^^^^^^^^^ cannot assign twice to immutable variable

Esto es porque las variables son inmutables si solo se escribe let.

Muchas veces, será necesario modificar la variable. Para ello, se debe añadir mut después de let:

fn main() {
    let mut mi_numero = 8;
    mi_numero = 10;
}

El código anterior funciona sin problema alguno.

Sin embargo, no se le puede cambiar el tipo. Esto no funciona:

fn main() {
    let mut mi_numero = 8; // La variable es i32. 
                           // El tipo no se puede cambiar.
    mi_numero = "¡Hola, mundo!"; // ⚠️
}

Si se intenta compilar el programa anterior, se obtendrá el mismo mensaje "expected" por parte del compilador: expected integer, found &str. &stres un tipo de cadena que aprenderemos pronto.

Ocultación (Shadowing)

La ocultación de una variable sucede cuando se usa let para declarar una nueva variable con el mismo nombre que otra. A primera vista se parece a la mutabilidad, pero es totalmente diferente. En el siguiente ejemplo, se oculta una variable:

fn main() {
    let mi_numero = 8; // Esta variable es i32
    println!("{}", mi_numero); // imprime 8
    let mi_numero = 9.2; // Esta variable es f64 y tiene el mismo nombre
    // pero es una variable nueva, completamente diferente.
    println!("{}", mi_numero) // imprime 9.2
}

Se dice que hemos "ocultado" mi_numerocon un nuevo "enlace".

¿Se ha destruido la anterior variable mi_numero? No, pero cuando se llama a mi_numero ahora se accede a la segunda variable de tipo f64. Y como ambas declaraciones se encuentran en el mismo bloque de código (mismo ámbito, mismo {}), se deja de tener acceso al mi_numero de tipo i32.

Si estuvieran en diferentes bloques de código, podríamos volver a acceder a la primera variable mi_numero. Por ejemplo:

fn main() {
    let mi_numero = 8; // Esta variable es i32
    println!("{}", mi_numero); // imprime 8
    {
        let mi_numero = 9.2; // Esta variable es f64 y tiene el mismo nombre
        // pero es una variable nueva, completamente diferente.
        println!("{}", mi_numero) // imprime 9.2
                                  // pero la nueva variable mi_numero
                                  // solo existe hasta aquí
                                  // la anterior ¡sigue viva!
    }
    println!("{}", mi_numero); // imprime 8
}

En resumen, cuando ocultas una variable, no la destruyes. La bloqueas.

Qué ventajas tiene el ocultar variables. Es una buena práctica cuando necesitas modificar una variable en muchas ocasiones. Imagina que quieres hacer un conjunto de cálculos matemáticos simples con una variable:

fn dos_veces(numero: i32) -> i32 {
    numero * 2
}

fn main() {
    let numero_final = {
        let y = 10;
        let x = 9; // x comienza con 9
        let x = dos_veces(x); // se oculta con el nuevo x: 18
        let x = x + y; // se oculta con el nuevo x: 28
        x // devuelve x: a numero_final se asigna este valor de x
    };
    println!("El número ahora es: {}", numero_final)
}

Sin ocultar las variables anteriores, habría sido necesario pensar diferentes nombres, incluso aunque no nos importen estos valores intermedios:

fn dos_veces(numero: i32) -> i32 {
    numero * 2
}

fn main() {
    // Ejemplo sin usar las capacidades de ocultar variables
    let numero_final = {
        let y = 10;
        let x = 9; // x comienza con 9
        let x2 = dos_veces(x); // segundo nombre para x
        let x2_y = x2 + y; // ¡tercer nombre para x!
        x2_y // qué pena no tener disponible la ocultación
             // habríamos podido usar solo una variable x
    };
    println!("El número ahora es: {}", numero_final)
}

En general, se usa la ocultación de variables en estos casos. Cuando se quiere usar una variable para un cálculo y luego otro más, sin tener mucho interés por los valores intermedios.