Aprende Rust

Lección 02 de 26 Primeros pasos

Variables y mutabilidad

  • 4 min de lectura
  • 4 ejercicios
  1. Una variable es una caja con nombre. let puntos = 10; guarda un 10 en la caja puntos.
  2. Por defecto es inmutable: la caja está cerrada con candado. puntos = 20; no compila.
  3. Con mut quitas el candado: let mut puntos = 10; permite cambiar el valor. Ahora puntos = 20; funciona.
  4. Shadowing: let x = x + 1; crea una caja nueva con el mismo nombre que tapa a la anterior.
1/4

Una variable es un nombre que le das a un valor para poder usarlo después. En Rust se declaran con let y tienen una particularidad que sorprende al principio: por defecto no se pueden modificar.

No es un capricho. Si un valor no cambia, es más fácil razonar sobre el código y el compilador puede garantizar que nadie lo modifica a tus espaldas. Cuando necesites que cambie, lo dirás de forma explícita.

Declarar variables con let

fn main() {
    let nombre = "Ferris";
    let edad = 7;

    println!("{nombre} tiene {edad} años");
}

No hace falta indicar el tipo: Rust lo infiere a partir del valor. Si quieres, puedes escribirlo tú después del nombre, separado por dos puntos:

fn main() {
    let edad: i32 = 7;
    println!("Edad: {edad}");
}

Verás los tipos con detalle en la siguiente lección.

Nombres: Las variables se escriben en snake_case: minúsculas y palabras separadas por guiones bajos, como puntos_totales. Si usas camelCase, el compilador te avisará.

Inmutables por defecto

Intenta cambiar el valor de una variable normal y el compilador se negará. Pulsa Ejecutar para ver el error:

fn main() {
    let puntos = 10;
    println!("Puntos: {puntos}");
    puntos = 20;
    println!("Puntos: {puntos}");
}
error[E0384]: cannot assign twice to immutable variable `puntos`
 --> src/main.rs:4:5
  |
2 |     let puntos = 10;
  |         ------ first assignment to `puntos`
3 |     println!("Puntos: {puntos}");
4 |     puntos = 20;
  |     ^^^^^^^^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable
  |
2 |     let mut puntos = 10;
  |         +++

Fíjate en el help: del final: el propio compilador te dice cómo arreglarlo.

Variables mutables con mut

Añade mut delante del nombre y la variable podrá cambiar de valor:

fn main() {
    let mut puntos = 10;
    println!("Puntos: {puntos}");

    puntos = 20;
    println!("Puntos: {puntos}");

    puntos += 5;
    println!("Puntos: {puntos}");
}

puntos += 5 es la forma corta de escribir puntos = puntos + 5. También existen -=, *=, /= y %=.

Ten en cuenta dos cosas:

  • mut permite cambiar el valor, pero no el tipo: si la variable guarda un número, siempre guardará un número.
  • Si marcas una variable como mut y nunca la modificas, el compilador te avisa con un warning: variable does not need to be mutable. Usa mut solo cuando lo necesites.

Constantes

Para valores fijos que no cambian nunca, como un límite o un dato de configuración, usa const:

const SEGUNDOS_POR_HORA: u32 = 60 * 60;
const MAX_VIDAS: u32 = 3;

fn main() {
    println!("Una hora tiene {SEGUNDOS_POR_HORA} segundos");
    println!("Empiezas con {MAX_VIDAS} vidas");
}

Las constantes se diferencian de las variables en varias cosas:

  • Siempre son inmutables: no admiten mut.
  • El tipo es obligatorio: no se infiere.
  • Se escriben en MAYÚSCULAS_CON_GUIONES_BAJOS.
  • Pueden declararse fuera de cualquier función, a nivel global.
  • Su valor tiene que poder calcularse al compilar, como 60 * 60.

Legibilidad: Puedes usar _ para separar las cifras de un número largo: 1_000_000 es lo mismo que 1000000, pero se lee mucho mejor.

Shadowing: redeclarar una variable

Puedes declarar una variable nueva con el mismo nombre que otra anterior. La nueva oculta a la anterior (en inglés, shadowing):

fn main() {
    let x = 5;
    let x = x + 1;
    let x = x * 2;

    println!("x vale {x}");
}

Cada let crea una variable nueva que se calcula a partir de la anterior. El resultado es 12 y x sigue siendo inmutable: nadie puede cambiarla por error más abajo.

La gran ventaja del shadowing es que la variable nueva puede tener otro tipo. Es muy útil cuando transformas un valor y no quieres inventar nombres como espacios_texto y espacios_numero:

fn main() {
    let espacios = "    ";
    let espacios = espacios.len();

    println!("Hay {espacios} espacios");
}

.len() devuelve la longitud del texto, así que espacios pasa de ser un texto a ser un número. Con mut esto no se puede hacer, porque el tipo no puede cambiar:

fn main() {
    let mut espacios = "    ";
    espacios = espacios.len();
    println!("Hay {espacios} espacios");
}
error[E0308]: mismatched types
 --> src/main.rs:3:16
  |
2 |     let mut espacios = "    ";
  |                        ------ expected due to this value
3 |     espacios = espacios.len();
  |                ^^^^^^^^^^^^^^ expected `&str`, found `usize`

Resumiendo las tres formas de dar nombre a un valor:

letlet mutconst
¿Puede cambiar de valor?NoSíNo
¿Tipo obligatorio?NoNoSí
¿Fuera de funciones?NoNoSí
Nombresnake_casesnake_caseMAYÚSCULAS

Ámbito de bloque

Una variable solo existe dentro del bloque { } en el que se declara, desde su let hasta la llave de cierre. Ese tramo es su ámbito (scope):

fn main() {
    let x = 5;

    {
        let x = x * 10;
        let y = 1;
        println!("Dentro: x = {x}, y = {y}");
    }

    println!("Fuera: x = {x}");
}

Dentro del bloque, el shadowing crea una x que vale 50. Al cerrar la llave esa x desaparece y vuelve a verse la original, que vale 5.

Lo mismo le pasa a y: al salir del bloque deja de existir. Si intentas usar una variable fuera de su ámbito, el código no compila:

fn main() {
    {
        let secreto = 42;
        println!("Dentro: {secreto}");
    }
    println!("Fuera: {secreto}");
}
error[E0425]: cannot find value `secreto` in this scope

Consejo: Si declaras una variable y no la usas, el compilador te avisa. Si es a propósito, empieza su nombre por guion bajo, como _temporal, y el aviso desaparece.

Hora de practicar

Ejercicios

0/4 resueltos

Reto 1 de 4

Este programa no compila porque vidas es inmutable. Arréglalo sin tocar la línea vidas -= 1; para que imprima Vidas: 3 y después Vidas: 2.
Salida esperada
Vidas: 3
Vidas: 2
src/main.rs
$ cargo run Listo

Pulsa Ejecutar para compilar y ver aquí la salida de tu programa.

¿Algo no cuadra en este reto? Avísanos