rust-closures

Closures en Rust: funciones anónimas y captura de contexto

  • 3 min

Un closure es una función anónima que puede capturar su entorno.

Es decir, un closure no solo recibe parámetros. También puede recordar datos que estaban alrededor, como quien se apunta una chuleta en la mano (pero legal y sin que te expulsen del examen).

Crear un closure

La sintaxis básica usa barras verticales |...| para los parámetros:

fn main() {
    let sumar_uno = |x| x + 1;

    println!("{}", sumar_uno(5));
}
Copied!

Esto se parece a una función, pero no hemos usado fn, ni hemos puesto tipo de retorno. Rust infiere los tipos a partir del uso.

Si queremos, podemos escribir los tipos explícitamente:

fn main() {
    let sumar = |a: i32, b: i32| -> i32 {
        a + b
    };

    println!("{}", sumar(2, 3));
}
Copied!

Closures frente a funciones

Una función normal se declara así:

fn sumar_uno(x: i32) -> i32 {
    x + 1
}
Copied!

Un closure se guarda en una variable:

let sumar_uno = |x| x + 1;
Copied!

La diferencia importante es que un closure puede capturar variables del entorno.

fn main() {
    let incremento = 10;
    let sumar_incremento = |x| x + incremento;

    println!("{}", sumar_incremento(5)); // 15
}
Copied!

sumar_incremento usa incremento, aunque incremento no sea un parámetro. Eso es la captura.

Captura por referencia

Por defecto, Rust intenta capturar de la forma menos invasiva posible.

fn main() {
    let nombre = String::from("Rust");

    let saludar = || {
        println!("Hola, {}", nombre);
    };

    saludar();
    println!("Seguimos teniendo nombre: {}", nombre);
}
Copied!

Aquí el closure solo necesita leer nombre, así que lo captura por referencia inmutable.

Captura mutable

Si el closure modifica una variable, necesita capturarla de forma mutable.

fn main() {
    let mut contador = 0;

    let mut incrementar = || {
        contador += 1;
        println!("contador = {}", contador);
    };

    incrementar();
    incrementar();
}
Copied!

El closure también tiene que ser mut, porque al llamarlo cambia su estado interno.

Captura con move

A veces queremos que el closure se lleve los valores consigo. Para eso usamos move.

fn main() {
    let mensaje = String::from("Hola desde otro sitio");

    let imprimir = move || {
        println!("{}", mensaje);
    };

    imprimir();
}
Copied!

Con move, el closure captura mensaje por valor y toma posesión de ese String. Esto es muy habitual cuando lanzamos hilos o tareas asíncronas, porque el closure puede ejecutarse más tarde, cuando la función original ya ha terminado.

move captura por valor, pero eso no siempre significa invalidar la variable original. Si el valor implementa Copy, el closure recibe una copia.

Closures e iteradores

Los closures brillan especialmente con iteradores:

fn main() {
    let numeros = vec![1, 2, 3, 4, 5];

    let pares: Vec<i32> = numeros
        .into_iter()
        .filter(|n| n % 2 == 0)
        .map(|n| n * 10)
        .collect();

    println!("{:?}", pares);
}
Copied!

filter y map reciben closures para saber qué hacer con cada elemento. Esto permite escribir transformaciones muy compactas, sin crear funciones auxiliares para todo.