Aprende Rust · 5 min

musl vs glibc: binarios estáticos y portables

Cómo compilar un binario de Rust totalmente estático con musl y cuándo conviene frente a glibc.

Todo programa de Rust en Linux usa la biblioteca C del sistema por debajo. Hay dos implementaciones habituales: glibc, la de la mayoría de distribuciones, y musl, una implementación más ligera. Cuál usas lo decide el target con el que compilas, y esa decisión cambia cómo se distribuye tu binario.

El target por defecto

En Linux, el target por defecto es x86_64-unknown-linux-gnu: usa glibc y enlaza de forma dinámica. Tu binario no lleva la biblioteca C dentro; la carga del sistema al arrancar. Es un target de Nivel 1 ("Tier 1"), el de máxima garantía de soporte.

El alternativo es x86_64-unknown-linux-musl: usa musl y permite un binario totalmente estático, sin dependencias de bibliotecas del sistema. Todo lo que el programa necesita viaja dentro del archivo. Es un target de Nivel 2.

Cómo compilar con musl

Primero agregas el target con rustup y luego compilas apuntando a él:

rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl

El resultado es un único archivo que puedes copiar a una imagen scratch o Alpine y ejecutar sin instalar nada más.

La cross-compilación puede complicarse cuando hay dependencias nativas en C. Una forma sencilla de evitar esa fricción es cargo-zigbuild, que usa el compilador de Zig como enlazador:

cargo install cargo-zigbuild
cargo zigbuild --release --target x86_64-unknown-linux-musl

glibc frente a musl

Criterioglibc (-gnu)musl (-musl)
EnlazadoDinámicoEstático (binario autocontenido)
Imagen de contenedorNecesita base con glibcCorre en scratch o Alpine, más pequeña
PortabilidadAtada a la versión de glibc del sistemaCorre igual en cualquier distribución
Asignador de memoriaRápido y maduroHistóricamente más lento (mitigable)
Compatibilidad con crates CMáximaAlguna crate nativa puede dar fricción
Resolución DNS/NSSSoporte completo de NSSImplementación propia, más limitada

Dos matices merecen detalle. El asignador de memoria de musl ha sido históricamente más lento que el de glibc en cargas con mucha reserva y liberación de memoria; si lo notas, puedes mitigarlo usando otro asignador como mimalloc o jemalloc desde tu propio programa. Y la resolución de nombres: glibc usa NSS, que permite configuraciones del sistema que musl no implementa igual, lo que en algunos entornos cambia cómo se resuelven los hosts.

glibc (-gnu): dinámicomusl (-musl): estáticoTu binariolibc.sold-linux.soLas carga el sistema al arrancar;deben existir y ser compatibles.Tu binario+ libc(todo dentro)Un solo archivo, sin dependencias;corre igual en scratch o Alpine.
Con glibc el binario carga bibliotecas del sistema al arrancar; con musl es autocontenido. La Referencia de Rust confirma que los objetivos *-musl enlazan de forma estática por defecto.

Cuándo usar cada uno

  • musl para microservicios en contenedor y CLIs que vas a distribuir. El binario estático simplifica la imagen, viaja entre distribuciones sin sorpresas y reduce la superficie de la imagen final. Encaja con lo que vemos en el rendimiento de un microservicio en Rust.
  • glibc por defecto para desarrollo local y cuando necesitas máxima compatibilidad o el mejor rendimiento del asignador. Es el camino con menos sorpresas si no tienes una razón para cambiar.

La regla práctica: desarrolla con glibc, distribuye con musl cuando el empaquetado lo justifique, y mide si el asignador te afecta antes de preocuparte por él.

Fuentes

  1. Platform Support - The rustc bookThe rustc book
  2. musl libcmusl
  3. Linkage - The Rust ReferenceThe Rust Reference
  4. GitHub - rust-cross/cargo-zigbuild: Compile Cargo project with zig as linkercargo-zigbuild

Construye mejor software.

Seguridad, eficiencia y oficio, con fuentes.