Rust 2024 Edition: let chains, gen blocks, async closures y cambios de la nueva edición

Rust lanza una nueva edición cada tres años. Las ediciones permiten introducir cambios incompatibles hacia atrás en la sintaxis sin romper el código existente: cada crate declara en su Cargo.toml con qué edición compila, y el compilador aplica las reglas de esa edición.

Rust 2024 se estabilizó en Rust 1.85 (febrero de 2025). Trae cuatro cambios principales: let chains, gen blocks, async closures con el trait AsyncFn y un cambio en cómo se declaran los bloques unsafe extern.

# Para usar la edición 2024:
[package]
name = "mi-crate"
edition = "2024"

Let chains: combinar if let con &&

Uno de los cambios más pedidos en Rust. Antes de la edición 2024, combinar múltiples condiciones if let requería anidar:

// Edición anterior: anidamiento forzado
fn procesar(config: &Config) {
    if let Some(usuario) = config.get_usuario() {
        if let Some(email) = usuario.get_email() {
            if email.contains('@') {
                println!("Email válido: {}", email);
            }
        }
    }
}

// Rust 2024: let chains con &&
fn procesar(config: &Config) {
    if let Some(usuario) = config.get_usuario()
        && let Some(email) = usuario.get_email()
        && email.contains('@')
    {
        println!("Email válido: {}", email);
    }
}

Los let chains también funcionan en while let:

while let Some(item) = iter.next()
    && item.es_valido()
    && let Ok(procesado) = procesar(item)
{
    guardar(procesado);
}

Y en las guardas de match (que ya eran posibles con if en ediciones anteriores, pero ahora se pueden combinar con let):

match valor {
    Opcion::Alguno(x) if let Ok(n) = x.parsear() && n > 0 => {
        println!("Número positivo: {}", n);
    }
    _ => {}
}

Gen blocks: iteradores con yield

Los gen blocks permiten crear iteradores usando la sintaxis yield, similar a los generadores de Python o JavaScript:

use std::iter::from_generator;  // o gen {} en nightly notation

// Sin gen blocks: implementar Iterator manualmente
struct Fibonacci {
    a: u64,
    b: u64,
}

impl Iterator for Fibonacci {
    type Item = u64;
    fn next(&mut self) -> Option<u64> {
        let siguiente = self.a + self.b;
        self.a = self.b;
        self.b = siguiente;
        Some(self.a)
    }
}

// Con gen blocks (Rust 2024):
let fibonacci = gen {
    let mut a = 0u64;
    let mut b = 1u64;
    loop {
        yield a;
        let siguiente = a + b;
        a = b;
        b = siguiente;
    }
};

for n in fibonacci.take(10) {
    print!("{} ", n);
}
// 0 1 1 2 3 5 8 13 21 34

Los gen blocks son especialmente útiles para iteradores que necesitan mantener estado complejo entre elementos:

fn leer_chunks(data: &[u8], tamaño: usize) -> impl Iterator<Item = &[u8]> {
    gen {
        let mut pos = 0;
        while pos < data.len() {
            let fin = (pos + tamaño).min(data.len());
            yield &data[pos..fin];
            pos = fin;
        }
    }
}

Async closures con AsyncFn

Antes de Rust 2024, escribir funciones genéricas que aceptaran closures async era complicado y requería workarounds:

// Antes: workaround con trait bounds complejos
async fn ejecutar_con_retry<F, Fut, T>(f: F) -> T
where
    F: Fn() -> Fut,
    Fut: Future<Output = T>,
{ /* ... */ }

// Rust 2024: AsyncFn
async fn ejecutar_con_retry<F: AsyncFn() -> T, T>(f: F) -> T {
    // f es directamente una async closure
    f().await
}

Los tres nuevos traits son: AsyncFn, AsyncFnMut y AsyncFnOnce, paralelos a Fn, FnMut y FnOnce.

use std::ops::AsyncFn;

async fn map_async<T, U, F: AsyncFn(T) -> U>(
    items: Vec<T>,
    f: F,
) -> Vec<U> {
    let mut resultado = Vec::with_capacity(items.len());
    for item in items {
        resultado.push(f(item).await);
    }
    resultado
}

// Uso con una async closure:
let resultados = map_async(vec![1, 2, 3], async |n| {
    tokio::time::sleep(Duration::from_millis(10)).await;
    n * 2
}).await;
// [2, 4, 6]

unsafe en bloques extern

En ediciones anteriores de Rust, podías importar funciones de C sin marcar el bloque como unsafe:

// Rust 2021 y anteriores:
extern "C" {
    fn malloc(size: usize) -> *mut u8;
}

// Rust 2024: los bloques extern deben ser unsafe explícitamente
unsafe extern "C" {
    fn malloc(size: usize) -> *mut u8;
    safe fn strlen(s: *const u8) -> usize;  // algunas funciones pueden marcarse como safe
}

El cambio hace más explícito que importar funciones de C es una operación unsafe. La palabra clave safe dentro de un bloque unsafe extern permite marcar funciones externas que el programador garantiza que son seguras de llamar sin restricciones.

Otros cambios de Rust 2024

match ergonomics mejoradas

// Rust 2024 afloja algunas restricciones de match ergonomics
// que podían requerir & explícitos en patrones
let referencia = &Some(42i32);
match referencia {
    Some(n) => println!("{}", n),  // n es &i32, sin necesidad de &Some(n)
    None => {}
}

Cambios en lifetime capture

En Rust 2024, los impl Trait en posición de retorno capturan todos los lifetimes de los parámetros de la función por defecto, que es el comportamiento más seguro. En ediciones anteriores había que usar impl Trait + '_ o similar para el mismo efecto.

Migrar a Rust 2024 con cargo fix

# Actualizar el toolchain
rustup update stable

# Migrar automáticamente
cargo fix --edition

# Cambiar en Cargo.toml:
# edition = "2024"

# Compilar para verificar
cargo check

La migración de edición 2021 a 2024 es generalmente automática y no rompe nada. El compilador guía los cambios necesarios. La mayoría del código no necesita modificaciones: los cambios principales son sintácticos y el compilador los aplica con cargo fix.

Las ediciones de Rust son una de sus mejores características de mantenimiento a largo plazo: el lenguaje puede evolucionar sin romper código existente, y los crates de distintas ediciones se combinan sin problemas en el mismo proyecto.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP