Los closures en Rust son funciones anónimas que pueden capturar variables del entorno donde se definen. A diferencia de otros lenguajes, Rust es explícito sobre cómo se captura ese entorno: por referencia inmutable, por referencia mutable o por valor. De esa captura depende cuál de los tres traits Fn, FnMut o FnOnce implementa el closure, y eso determina dónde y cuántas veces puedes llamarlo.
Sintaxis y tipos inferidos
fn main() {
// Función normal
fn cuadrado(x: i32) -> i32 { x * x }
// Closure con tipos explícitos
let cuadrado_c = |x: i32| -> i32 { x * x };
// El compilador infiere los tipos
let cuadrado_i = |x| x * x;
println!("{}", cuadrado(5)); // 25
println!("{}", cuadrado_c(5)); // 25
println!("{}", cuadrado_i(5)); // 25
// Closure de una expresión: las llaves son opcionales
let doble = |x| x * 2;
println!("{}", doble(7)); // 14
}
Captura del entorno
fn main() {
let factor = 3;
// Captura por referencia inmutable (&factor)
let multiplica = |x| x * factor;
println!("{}", multiplica(5)); // 15
println!("factor sigue: {factor}"); // 3
let mut contador = 0;
// Captura por referencia mutable (&mut contador)
let mut incrementar = || {
contador += 1;
println!("contador: {contador}");
};
incrementar(); // 1
incrementar(); // 2
// println!("{contador}"); // ERROR: mientras exista `incrementar`, contador está prestado
drop(incrementar);
println!("final: {contador}"); // 2
}
move: capturar por valor
use std::thread;
fn main() {
let mensaje = String::from("hola desde el hilo");
// move fuerza que el closure tome propiedad de mensaje
let h = thread::spawn(move || {
println!("{mensaje}");
});
// println!("{mensaje}"); // ERROR: mensaje fue movido al closure
h.join().unwrap();
}
Fn, FnMut y FnOnce
Rust asigna automáticamente el trait más restrictivo que necesita el closure:
- FnOnce: puede llamarse una sola vez. Consume variables capturadas.
- FnMut: puede llamarse varias veces y muta el entorno capturado.
- Fn: puede llamarse varias veces sin mutar nada.
fn llamar_una_vez<F: FnOnce() -> String>(f: F) {
println!("{}", f());
// f(); // ERROR: f ya fue consumido
}
fn llamar_varias<F: Fn() -> i32>(f: F, veces: u32) {
for _ in 0..veces {
println!("{}", f());
}
}
fn acumular<F: FnMut(i32) -> i32>(mut f: F, valores: &[i32]) -> Vec<i32> {
valores.iter().map(|&v| f(v)).collect()
}
fn main() {
let s = String::from("consumido");
llamar_una_vez(move || s); // FnOnce: consume s
llamar_varias(|| 42, 3); // Fn: solo lee
let mut suma = 0;
let resultados = acumular(|x| { suma += x; suma }, &[1, 2, 3, 4]);
println!("{resultados:?}"); // [1, 3, 6, 10]
}
impl Fn vs Box<dyn Fn> como parámetros
// impl Fn: genérico, resuelto en compilación (zero-cost)
fn aplicar_impl(f: impl Fn(i32) -> i32, x: i32) -> i32 {
f(x)
}
// Box<dyn Fn>: despacho dinámico, sirve para guardar closures en structs
fn crear_multiplicador(n: i32) -> Box<dyn Fn(i32) -> i32> {
Box::new(move |x| x * n)
}
struct Pipeline {
pasos: Vec<Box<dyn Fn(i32) -> i32>>,
}
impl Pipeline {
fn new() -> Self { Self { pasos: vec![] } }
fn add(mut self, f: impl Fn(i32) -> i32 + 'static) -> Self {
self.pasos.push(Box::new(f));
self
}
fn run(&self, entrada: i32) -> i32 {
self.pasos.iter().fold(entrada, |acc, f| f(acc))
}
}
fn main() {
let triple = crear_multiplicador(3);
println!("{}", triple(5)); // 15
let resultado = Pipeline::new()
.add(|x| x + 1)
.add(|x| x * 2)
.add(|x| x - 3)
.run(10);
println!("{resultado}"); // (10+1)*2-3 = 19
}
Closures que acumulan estado
fn contador_desde(inicio: i32) -> impl FnMut() -> i32 {
let mut n = inicio;
move || {
let actual = n;
n += 1;
actual
}
}
fn main() {
let mut c = contador_desde(5);
println!("{}", c()); // 5
println!("{}", c()); // 6
println!("{}", c()); // 7
}
Resumen
- Los closures capturan el entorno por referencia, referencia mutable o valor (
move). Fn: solo lee el entorno; puede llamarse sin límite.FnMut: muta el entorno; puede llamarse sin límite.FnOnce: consume el entorno; solo puede llamarse una vez.- Usa
impl Fnpara parámetros de función genéricos (más rápido). - Usa
Box<dyn Fn>cuando necesitas guardar closures en estructuras de datos. movees obligatorio cuando el closure cruza límites de hilo.
