¿Cómo diferenciar debug de release en Rust? Analizamos cfg con ejemplos reales
\n\n¿Alguna vez has escrito código en Rust y has pensado: «Ojalá en modo debug añadir un registro detallado, y en release eliminar todo lo superfluo»? ¿O quizás querías incluir aserciones solo durante el desarrollo, pero desactivarlas en la compilación final? Si es así, has llegado al lugar indicado. Hoy analizaremos cómo, con la magia de cfg, controlar el comportamiento del código según el tipo de compilación. Esto no es solo teoría árida: recorreremos el camino desde una simple bandera hasta una solución lista para producción.
El tema no surge de la nada: en Stack Overflow, la pregunta «How to check release / debug builds using cfg in Rust?» ha acumulado miles de visitas. Esto significa que muchos lo necesitan. Y lo explicaremos en detalle.
\n\n¿Por qué es importante? Dos mundos, dos enfoques
\n\nEn Rust existen dos perfiles principales de compilación: debug (con la bandera --debug o simplemente cargo build) y release (cargo build --release). Se diferencian no solo en las optimizaciones, sino también en el comportamiento de algunas verificaciones integradas.
Por ejemplo:
\n- \n
- debug: lento, pero con verificaciones de límites de arreglos, símbolos de depuración y aserciones. \n
- release: rápido, pero sin verificaciones adicionales, todo en aras del rendimiento. \n
¿Qué pasa si necesitas que en la versión debug el programa registre cada acción, y en release solo los errores críticos? ¿O que un endpoint de prueba solo esté disponible localmente? Para eso existe cfg.
Herramientas principales: cfg!() y #[cfg()]
\n\nEn Rust hay dos formas de verificar el tipo de compilación:
\n- \n
- La macro
cfg!()— se ejecuta durante la compilación, devuelve unbool. Se usa dentro de funciones. \n - El atributo
#[cfg()]— incluye o excluye completamente un bloque de código de la compilación. \n
Ambos usan las mismas claves. La más popular para nuestra tarea es debug_assertions. Está activa solo en la compilación debug. También existe la inversa: not(debug_assertions) para release.
Ejemplo 1: Verificación simple dentro de una función
\n\nEmpecemos con la macro cfg!(). Supongamos que tenemos una función que calcula algo complejo. En debug queremos verificar los datos de entrada:
fn calculate_something(x: i32) -> i32 {\n // Verificación solo en compilación debug\n if cfg!(debug_assertions) {\n println!("[DEBUG] calculate_something llamada con x = {}", x);\n assert!(x > 0, "¡x debe ser positivo!");\n }\n\n // Lógica principal\n x * 2\n}\n\nfn main() {\n let result = calculate_something(10);\n println!("Resultado: {}", result);\n}\n\n\n¿Qué sucederá?
\n- \n
- Con
cargo build(debug) — verás el registro y, si x ≤ 0, el programa fallará con una aserción. \n - Con
cargo build --release— la línea concfg!(debug_assertions)se convertirá enfalse, y el compilador eliminará este bloque como código muerto. ¡Sin sobrecarga! \n
Ejemplo 2: Exclusión total de código con #[cfg()]
\n\nA veces no basta con saltarse la ejecución, sino que se necesita eliminar por completo una función o un módulo del binario. Para eso usamos el atributo:
\n\n// Este módulo se compilará SOLO en compilación debug\n#[cfg(debug_assertions)]\nmod debug_helpers {\n pub fn print_state(state: &str) {\n println!("[DEBUG] Estado actual: {}", state);\n }\n\n pub fn validate_data(data: &[u8]) -> bool {\n // Verificación costosa, no necesaria en release\n data.len() < 1000\n }\n}\n\n// Este módulo es solo para release\n#[cfg(not(debug_assertions))]\nmod release_helpers {\n pub fn print_state(_state: &str) {\n // En release no hacemos nada — ahorramos CPU\n }\n\n pub fn validate_data(_data: &[u8]) -