wandres.dev
SETUP Y CARGO · rustup, cargo, edición 2024

La edición 2024 de Rust

Qué es una edición y cómo Rust evoluciona sin fragmentar su ecosistema, y qué trae la 2024: async closures, captura precisa en impl Trait, el resolver v3 con conciencia de MSRV y el horizonte Polonius.

⏱ 16 min

Casi ningún lenguaje resuelve bien la tensión entre evolucionar y no romper el código existente: Python tardó una década en digerir el salto del 2 al 3. Rust inventó las ediciones para tener las dos cosas a la vez, y la 2024 es la cuarta. Entender el mecanismo importa más que memorizar la lista de novedades.

🎯 Al terminar esta lección sabrás
  • Entender qué es una edición y qué puede y no puede cambiar.
  • Ver cómo crates de ediciones distintas interoperan sin fricción.
  • Conocer las novedades que trae la edición 2024.
  • Migrar un proyecto con cargo fix.

Qué es una edición

Una edición es un conjunto de cambios opt-in que pueden romper la sintaxis de superficie —introducir palabras clave, cambiar valores por defecto o idiomas— sin afectar a quien no la adopta. Se declara por crate en el manifiesto:

[package]
name = "hola"
edition = "2024"

La clave está en lo que una edición no cambia. Hay un solo compilador, que entiende todas las ediciones (2015, 2018, 2021 y 2024). La librería estándar es única y compartida: un Vec<T> es el mismo en todas. Una edición no puede alterar el sistema de tipos ni la std de forma incompatible; solo reescribe la superficie del lenguaje.

Conviene tener claro el reparto:

  • Puede cambiar: palabras clave reservadas, valores por defecto de lints, resolución de nombres, algunos idiomas y azúcar sintáctico.
  • No puede cambiar: el sistema de tipos, la ABI, la std, ni nada que impida enlazar crates de ediciones distintas.

Por eso una edición nunca es una versión del compilador: el compilador de la serie 1.9x de 2026 sigue compilando crates de 2015 con sus reglas de 2015.

flowchart LR
E15[2015 la base] --> E18[2018 modulos y async await]
E18 --> E21[2021 closures y prelude]
E21 --> E24[2024 captura y resolver v3]

Interoperar sin fragmentar

Aquí está la genialidad. El compilador compila cada crate según su propia edición. Tu crate en 2024 puede depender de una librería escrita en 2015, y esa librería puede a su vez depender de otra en 2021. Todas se enlazan en el mismo binario sin problema.

[dependencies]
crate_viejo = "1.0"   # quizá edición 2015; da igual
crate_nuevo = "2.0"   # quizá edición 2024

Esto disuelve el dilema que hundió a otros lenguajes: no hay un “día del cambio” en el que todo el ecosistema deba migrar a la vez. Cada crate migra cuando su equipo quiere, y mientras tanto todo sigue compilando. La actualización es descentralizada.

En la práctica, puedes subir tu crate a 2024 hoy sin esperar a que tus dependencias lo hagan, y sin que ellas te esperen a ti. El grafo de dependencias mezcla ediciones con total naturalidad, y el binario final no sabe —ni le importa— en qué edición se escribió cada pieza.

Qué trae la edición 2024

La 2024 se estabilizó con Rust 1.85 (febrero de 2025) y es la edición más grande hasta la fecha. Sus piezas más relevantes:

Async closures

Ya puedes escribir async || { ... } y usar los traits AsyncFn, AsyncFnMut y AsyncFnOnce. Cierra un hueco viejo del async: pasar closures que devuelven futuros con préstamos correctos.

🎯

Captura precisa en impl Trait

En un -> impl Trait, ahora se capturan por defecto todos los parámetros de tipo y de lifetime en alcance. Con + use<'a, T> decides exactamente cuáles. Elimina una clase entera de errores confusos de lifetimes.

🧮

Resolver v3 (MSRV)

El resolvedor de dependencias pasa a resolver = "3", consciente de la versión mínima: no elige una dependencia que exija un compilador más nuevo que el declarado en rust-version.

🔒

unsafe explícito

Los bloques extern pasan a ser unsafe extern, y atributos peligrosos se escriben #[unsafe(no_mangle)]. La palabra gen queda reservada para la futura sintaxis de generadores.

Las async closures son la novedad más visible. Antes, una closure que devolvía un futuro obligaba a contorsiones con move y lifetimes; ahora el lenguaje las entiende de forma nativa:

// AsyncFn describe algo llamable que devuelve un futuro:
async fn procesa<F: AsyncFn(u32) -> u32>(f: F) {
    let r = f(21).await;               // llamar y esperar el futuro
    println!("resultado: {r}");
}

// Una async closure que captura `factor` del entorno:
let factor = 2;
let doblar = async |x| x * factor;     // implementa AsyncFn

Un ejemplo de la captura precisa, el cambio más sutil:

// En 2024, este impl Trait captura 'a automáticamente.
fn nombres<'a>(v: &'a [String]) -> impl Iterator<Item = &'a str> {
    v.iter().map(|s| s.as_str())
}

// Y con use<> acotas la captura a lo estrictamente necesario:
fn ids<'a>(v: &'a [u32]) -> impl Iterator<Item = u32> + use<'a> {
    v.iter().copied()
}

La 2024 endurece además la seguridad por defecto: tomar una referencia a static mut pasa a ser un error, el prelude incorpora Future e IntoFuture para que el async pese menos, y el scope de los temporales en if let se acorta para cerrar un viejo foco de fugas sutiles. Ninguno rompe nada: cargo fix los adapta por ti.

📝
Polonius no es de la edición

El horizonte 2024 se asocia también a Polonius, el borrow checker de nueva generación que reformula el análisis de préstamos para aceptar programas correctos que el actual rechaza (el caso clásico: devolver una referencia dentro de un bucle). Pero conviene precisar: los avances del borrow checker no dependen de la edición. Solo aceptan más programas válidos, nunca invalidan código que ya compilaba, así que se aplican a todas las ediciones por igual. Su versión “alpha” —un análisis sensible a la ubicación, superconjunto del actual— madura en nightly con vistas a estabilizarse. Polonius llega por el compilador, no por el campo edition.

Migrar a la 2024

La migración está semiautomatizada. Cargo reescribe tu código para que siga funcionando bajo las nuevas reglas y solo entonces subes el campo edition:

cargo fix --edition        # aplica las reescrituras compatibles
# ahora edita Cargo.toml: edition = "2024"
cargo build                # confirma que todo compila bajo 2024

El flujo recomendado es migrar en dos pasos: primero cargo fix --edition con el árbol limpio en git para revisar el diff, y después, ya en la nueva edición, cargo fix --edition-idioms para adoptar los modismos actuales. Como cada crate migra por separado, en un workspace grande puedes hacerlo crate a crate sin bloquear al resto.

💡
Empieza siempre en la última edición

Un proyecto nuevo debe nacer en 2024: cargo new ya la pone por defecto. No hay ninguna ventaja en arrancar en una edición vieja, y sí el coste de migrar más tarde. La regla es simple: crea en la última, migra las viejas cuando puedas.

Las ediciones son el mejor invento de gobernanza de Rust

Cualquier lenguaje vivo se enfrenta al mismo dilema: si nunca rompe nada, acumula décadas de deuda sintáctica que no puede tocar; si rompe para mejorar, parte a su comunidad en dos y provoca un cisma como el de Python 2 y 3. Rust escapó de la trampa con una idea de ingeniería de sistemas aplicada al propio lenguaje: hacer de la edición un dato por crate, resuelto en compilación, con un único compilador que habla todas las ediciones y una std compartida por todas. El resultado es que Rust puede introducir palabras clave nuevas, cambiar valores por defecto peligrosos y limpiar idiomas viejos cada tres años, sin que un solo crate del ecosistema deje de compilar el día del lanzamiento. La compatibilidad deja de ser una restricción que frena la evolución y pasa a ser una propiedad diseñada para que la evolución sea barata. Entender esto es entender por qué Rust puede prometer estabilidad y progreso a la vez, algo que la mayoría de los lenguajes considera una contradicción.

⚔️ Vive una edición
  1. En un Cargo.toml, cambia edition entre "2021" y "2024" y observa qué avisos aparecen.
  2. Escribe una función -> impl Iterator que devuelva referencias y comprueba cómo se comporta la captura en 2024.
  3. Añade rust-version a tu manifiesto y razona qué haría el resolver v3 con una dependencia demasiado nueva.
  4. Ejecuta cargo fix --edition sobre un proyecto en 2021 y lee el diff que genera.
  5. Investiga una novedad de la 2024 que no salga aquí (por ejemplo, el cambio de scope de los temporales en if let).