← Todos los artículos

Rust 1.99: cómo comprobar el cambio de Cargo en CI antes de tocar tu caché

Rust 1.99 desactiva por defecto la compilación incremental de Cargo en CI. Aprende a medir tu pipeline y decidir una política explícita.

2 de octubre de 2026 · · 8 min de lectura
Un taller de compilación sobre una plataforma flotante recibe bloques de una caché separada y compara la reutilización con una compilación nueva.

El equipo de Rust publicó la versión 1.99.0 el 1 de octubre de 2026. Entre sus cambios de Cargo hay uno operativo: cuando Cargo encuentra la variable de entorno CI, el valor predeterminado de la compilación incremental pasa a ser false. El cambio no elimina la capacidad ni obliga a todos los proyectos a desactivarla; modifica la decisión automática para un entorno donde el runner puede ser efímero y la caché puede costar más de lo que ahorra.

La pregunta útil para un equipo backend no es «¿incremental siempre sí o siempre no?». Es: «¿qué combinación de perfil, variable, directorio target/ y caché produce el menor tiempo total en nuestro pipeline, sin confundir pruebas con el artefacto de producción?».

Qué cambia en Cargo 1.99

La nota de Rust 1.99.0 registra que la compilación incremental queda desactivada por defecto en CI y que la detección se basa en la variable CI. La documentación de configuración de Cargo lo concreta: si CI está definida y no se ha fijado otro valor, el valor efectivo de build.incremental es false; sin esa señal, se usa el valor del perfil. CARGO_BUILD_INCREMENTAL es la variable de configuración equivalente y puede fijar explícitamente ese valor.

«Está definida» importa. El cambio implementado en Cargo consulta una detección de CI cuando no hay una configuración más específica; su prueba establece CI a 1 y comprueba que no se pase -C incremental a rustc. Ni la documentación ni esa prueba dicen que Cargo espere literalmente CI=true. Por eso conviene comprobar qué variable exporta realmente tu proveedor y no crear una condición propia que solo funcione con el texto true.

La precedencia práctica es esta:

  1. CARGO_INCREMENTAL explícita (1 fuerza incremental; 0 la desactiva).
  2. La configuración de build, por ejemplo build.incremental en .cargo/config.toml o su variable equivalente CARGO_BUILD_INCREMENTAL.
  3. La señal CI, que pone el valor predeterminado en desactivado.
  4. El valor del perfil seleccionado.

No se borran archivos de target/, no se crea una caché persistente y no se comparten artefactos automáticamente entre runners. Solo cambia el valor que Cargo elige cuando no hay una decisión más explícita.

Dev y release no parten del mismo valor

La compilación incremental guarda información adicional en target/ para reutilizarla en recompilaciones. Solo se usa para miembros del workspace y dependencias de ruta, no como una caché universal de todas las dependencias descargadas.

Los perfiles integrados tienen objetivos distintos. cargo build, cargo check y cargo run usan dev si no se indica otro perfil; ese perfil parte de incremental = true. cargo build --release usa release, cuyo valor predeterminado es incremental = false. cargo test usa el perfil test, que hereda de dev.

Si quieres declarar el supuesto de desarrollo en el manifiesto, puedes hacerlo así:

[profile.dev]
incremental = true

No conviertas esa línea en una recomendación para el artefacto de producción. La referencia de perfiles confirma los valores predeterminados y explica que CARGO_INCREMENTAL puede sobrescribirlos globalmente, incluso en perfiles que parten de otro valor. Esa es una razón para separar los comandos de pruebas y compilación final cuando compares resultados.

Comprueba primero la política efectiva

Fija el toolchain en el proyecto, por ejemplo con un rust-toolchain.toml que indique 1.99.0, y usa cargo test --locked para evitar que la prueba cambie el grafo de dependencias. Para inspeccionar la decisión de compilación, ejecuta con salida detallada (-vv) y conserva el log de rustc. La presencia de una ruta -C incremental=... en la orden de compilación es una señal útil; su ausencia no debe interpretarse fuera del comando y el perfil que acabas de probar.

Puedes aislar cada escenario en un directorio de destino temporal. Cada ejecución genera una carpeta nueva bajo %TEMP%/$TMPDIR y usa un target/ distinto para el valor automático y para el override explícito; no ejecuta cargo clean ni elimina el target/ del proyecto:

$runId = [guid]::NewGuid().ToString("N")
$exp = Join-Path $env:TEMP "rust-1-99-cargo-$runId"
$env:CI = "1"                 # la señal está presente; no depende de CI=true
Remove-Item Env:CARGO_INCREMENTAL -ErrorAction SilentlyContinue

$env:CARGO_TARGET_DIR = Join-Path $exp "target-default"
New-Item -ItemType Directory -Force -Path $env:CARGO_TARGET_DIR | Out-Null
cargo build --locked -vv

$env:CARGO_INCREMENTAL = "1" # control explícito
$env:CARGO_TARGET_DIR = Join-Path $exp "target-forced"
New-Item -ItemType Directory -Force -Path $env:CARGO_TARGET_DIR | Out-Null
cargo build --locked -vv

En Bash, la misma idea es:

exp="$(mktemp -d "${TMPDIR:-/tmp}/rust-1-99-cargo.XXXXXX")"
mkdir -p "$exp/target-default" "$exp/target-forced"
export CI=1
unset CARGO_INCREMENTAL
export CARGO_TARGET_DIR="$exp/target-default"
cargo build --locked -vv
CARGO_INCREMENTAL=1 CARGO_TARGET_DIR="$exp/target-forced" cargo build --locked -vv

Para una comparación limpia, usa un directorio temporal distinto por escenario y registra el tiempo de Cargo, el tamaño de ese directorio y si se restauró una caché antes de ejecutar el comando. Una segunda ejecución en el mismo directorio responde a una pregunta distinta de la primera: mide la reutilización local, no el coste de un runner recién creado.

Tres escenarios que no debes mezclar

Escenario Qué mide Lectura que sí se puede hacer
Runner efímero, CI presente, sin CARGO_INCREMENTAL Compilación sin estado incremental recuperado La política predeterminada evita escribir y restaurar estado incremental; no demuestra que el build completo sea más rápido.
Runner con target/ restaurado, CI presente, sin override Reutilización de artefactos que tu estrategia de caché realmente conserva Compara tiempo de restauración más compilación contra el tamaño y la tasa de aciertos de la caché.
CARGO_INCREMENTAL=1 explícita Coste y beneficio de forzar incremental Solo es una buena política si las recompilaciones y el coste de guardar/restaurar target/ mejoran el tiempo total.

No hay cifras universales que rellenar en esta tabla. Un resultado medido debe conservar al menos el sistema operativo, target, versión exacta de Rust, clave de caché, si fue un cold o warm run, tiempo de restauración, tiempo de Cargo, tamaño de target/, número de ejecuciones y estado de salida. Por ejemplo, reporta por separado «runner efímero: primera compilación» y «runner con caché: restauración + segunda compilación»; no presentes el segundo tiempo como si fuera el de una máquina limpia.

Haz dos mediciones de recompilación en cada escenario, después de un primer build estable. Una puede tocar un archivo de un miembro del workspace y la otra una entrada que fuerce recompilar más crates. Así sabrás si incremental ayuda al cambio que realmente hacen tus desarrolladores, en vez de optimizar una edición artificial.

Separa pruebas y artefacto de producción

cargo test --locked usa test, que hereda de dev. Es una buena candidata para estudiar recompilaciones durante el feedback del equipo, siempre que la caché se mida con el mismo target y toolchain que la CI real.

El binario que se empaqueta debe medirse aparte:

# Comprobación de la suite, con la política que hayas decidido para CI
CARGO_INCREMENTAL=0 cargo test --locked

# Artefacto de producción: release parte sin incremental
CARGO_INCREMENTAL=0 cargo build --locked --release

Estos comandos hacen explícita una política de prueba, no prueban que 0 sea la mejor opción para tu organización. Si decides probar CARGO_INCREMENTAL=1, repite las dos órdenes en el directorio temporal de ese escenario y compara también el tamaño de la caché. No extrapoles el tiempo de cargo test al tiempo del binario final: seleccionan perfiles diferentes y tienen objetivos diferentes.

Cómo elegir una política

Conserva el valor automático de 1.99 si tus runners son efímeros, la caché de target/ tiene pocos aciertos o el coste de comprimir y transferir estado supera la mejora de las recompilaciones. Es la opción con menos configuración y deja dev incremental cuando trabajas fuera de CI.

Prueba CARGO_INCREMENTAL=1 solo si el pipeline restaura target/ de forma confiable y tus mediciones muestran que las recompilaciones compensan el tamaño y la latencia de la caché. Usa una clave que distinga, como mínimo, sistema operativo, target, versión del toolchain y el hash pertinente de Cargo.lock y configuración. No guardes indiscriminadamente directorios producidos por otras combinaciones.

Fuerza CARGO_INCREMENTAL=0 si necesitas que una etapa sea explícitamente no incremental o si tus mediciones muestran que el estado extra añade coste sin reutilización. Mantén la decisión visible en el job y registra qué comandos quedan afectados: la variable puede sobrescribir el valor del perfil y, por tanto, cambiar más comandos de los que pretendías.

Checklist para subir a Rust 1.99

  • Fija 1.99.0 y comprueba rustc --version y cargo --version en el mismo job que mide.
  • Confirma si el proveedor exporta CI y qué valor tiene; Cargo documenta la presencia de la variable, no una cadena universal.
  • Ejecuta cargo test --locked y cargo build --locked --release como experimentos separados.
  • Compara una compilación limpia y al menos dos recompilaciones por escenario.
  • Separa restauración de caché, compilación y pruebas en las métricas.
  • Revisa advertencias nuevas y ejecuta la matriz de targets que el servicio soporta.
  • Conserva el valor automático, CARGO_INCREMENTAL=1 y CARGO_INCREMENTAL=0 como controles comparables antes de fijar una política.

Rust 1.99 no convierte la compilación incremental en una garantía de rendimiento. Convierte una decisión implícita de CI en algo que merece medirse: qué conserva el runner, qué restaura la caché y cuánto tarda cada perfil. Con esa evidencia, dev puede seguir siendo ágil para iterar, release puede seguir produciendo un artefacto no incremental por defecto y la CI puede tener una política explícita que responda a sus propios datos.

Fuentes primarias

Comentarios

Cargando…