# zlib-rs 0.6.8 — `libz` en Rust, con la MISMA ABI de C. Variante para reemplazar a `zlib`. # # ── POR QUÉ ESTÁ EN EL CATÁLOGO Y NO EN NINGÚN PERFIL ──────────────────────────────────────────── # `zlib` es la librería C **más consumida del corpus**: 122 recetas la declaran (más 83 la variante # `-shared`). Eso la vuelve la pieza donde la seguridad de memoria compra más… y también la más cara # de cambiar: el swap re-hashea esas 122. Esta receta existe para que la decisión se pueda tomar con # el artefacto delante, no en abstracto — el plan lo pedía así (`docs/plan-que-sigue-tras-busybox.md` # paso 8: «receta que publique el mismo SONAME, swap en UN consumidor, y comparar»). # # ── LO MEDIDO (2026-09-22, corpus Silesia de 15,7 MB, el mismo binario de prueba con las dos libs) ─ # # nivel │ zlib 1.3.1 │ zlib-rs 0.6.8 │ tamaño │ tiempo # ──────┼────────────────────────────┼────────────────────────────┼──────────┼───────── # 1 │ 7.050.606 B · 226,6 ms │ 8.526.731 B · 95,1 ms │ +20,9 % │ 2,4× más rápido # 6 │ 6.409.962 B · 760,4 ms │ 6.457.822 B · 335,4 ms │ +0,7 % │ 2,3× más rápido # 9 │ 6.371.670 B · 1496,3 ms │ 6.358.939 B · 634,9 ms │ −0,2 % │ 2,4× más rápido # # Round-trip correcto en los tres niveles. O sea: al nivel POR DEFECTO cuesta 0,7 % de tamaño y # devuelve 2,3× de velocidad; al 9 es más chica Y más rápida; al 1 comprime bastante peor. # # ⚠ **NO es idéntica byte a byte.** El `fnv` de la salida difiere en los tres niveles. Consecuencia # que hay que decir antes de que alguien se sorprenda: cualquier artefacto que EMBEBA datos # comprimidos (páginas de manual gzipeadas, PNG, `.tar.gz`, un initramfs) **cambia de contenido** al # hacer el swap. No es no-determinismo —es determinista y distinto—, pero significa que la campaña de # las 122 no es «reconstruir y obtener lo mismo»: produce artefactos DISTINTOS. # # ⚠ **`gzprintf`/`gzvprintf` NO están.** Medido comparando símbolos contra `libz.a` del corpus: de los # 101 públicos faltan esos dos, y su feature en upstream **pide Rust nightly** (`c_variadic`). El # resto de la lista que falta es interna de zlib (`_tr_*`, `inflate_fast`, `inflate_table`, `zcalloc`, # `z_errmsg`), que ningún consumidor debería usar. Un consumidor que llame `gzprintf` NO enlaza. # # ⚠ **Quien enlace esto necesita `-lunwind`**, porque es Rust adentro de C: el `staticlib` arrastra el # backtrace de std (`addr2line`/`gimli`) y sin el unwinder el enlace muere con `undefined symbol: # _Unwind_Resume`. Es la perilla que el corpus ya conoce ([[lab-cargo-vendor-poliglota]]); zig la # provee. Por eso esta receta **no** se puede declarar como dep y ya: el consumidor tiene que sumar # `LIBS=-lunwind` o su equivalente. # # ⚠ **LA `.so` NO DECLARA SONAME SOLA, y sin eso es inservible para un consumidor dinámico** # (medido 2026-09-22): `readelf -d` sobre la `libz_rs.so` que sale de cargo muestra `NEEDED` y ningún # `SONAME`, mientras la `zlib-shared` del corpus declara `libz.so.1`. Un consumidor que enlace contra # una `.so` sin soname graba en su `NEEDED` **la ruta con la que se enlazó**, no el nombre de la # librería ⇒ el binario sella, reproduce y **no corre** ([[needed-colgante-libstdcxx]]). Se arregla # con `-C link-arg=-Wl,-soname,libz.so.1` en el compile, que es donde el hash lo cubre. # # ⚠ **`zlibVersion()` contesta `1.3.0-zlib-rs-0.6.8`.** El chequeo de versión de `deflateInit_` compara # sólo el primer carácter, así que pasa; un `configure` que parsee la cadena entera puede no gustarle. # # ── EL BUILD, Y POR QUÉ LLEVA UN PARCHE DE DOS LÍNEAS ─────────────────────────────────────────── # El crate que publica la ABI de C es `libz-rs-sys-cdylib`, y upstream lo tiene **excluido** del # workspace. Compilarlo desde su directorio no sirve acá: el vendoreo del lab corre en la RAÍZ, así # que un `cargo build --offline` adentro del subdirectorio no encuentra las deps del crate excluido # («perhaps a crate was updated and forgotten to be re-vendored?»). Y meterlo al workspace sin más # tampoco: el crate aborta a propósito con # # compile_error!("panic=\"abort\" is mandatory because unwinding to C is undefined behavior"); # # porque el `[profile.release]` de un MIEMBRO se ignora (gana el de la raíz) — que es exactamente por # lo que upstream lo excluyó. El parche hace las dos cosas juntas: sube el crate a `members` y pone # `panic = "abort"` en el profile de la RAÍZ. Entra en `hash_inputs` (los parches sí entran), que es # donde tiene que estar una decisión de build. # # ⚠ Y toca **también el `Cargo.lock`**, porque el vendoreo del lab corre con `--locked`: con un miembro # nuevo el lock cambia y cargo se niega a actualizarlo («cannot update the lock file … because # --locked was passed»). El añadido son las 8 líneas del paquete nuevo; el lock sigue pineado, que es # la premisa del ADR 0006. # # `--features libz-rs-sys-cdylib/gz` exporta la familia `gz*`, que sí usan consumidores de verdad. name = "zlib-rs" version = "0.6.8" license = "Zlib" [source] repo = "https://github.com/trifectatechfoundation/zlib-rs" # tag v0.6.8. El commit es el identificador inmutable (ADR 0006), no el tag. commit = "0254e07204884bfc902d6ebb68c8b703d0ea49cf" patches = ["zlib-rs-workspace.patch"] [build] compiler = "zig-cc" target = "x86_64-linux-musl" link = "static" flags = [] [build.phases] configure = "true" # `RUSTFLAGS` con el soname: acá es seguro porque este árbol NO tiene proc-macros (el problema # documentado de `+crt-static` en RUSTFLAGS) y el único artefacto enlazado es la propia cdylib. compile = ''' RUSTFLAGS="-C link-arg=-Wl,-soname,libz.so.1" \ cargo build --release --locked --offline -p libz-rs-sys-cdylib --features libz-rs-sys-cdylib/gz ''' # Se instala con los NOMBRES de zlib (`libz.a`, `libz.so.1`) y con los headers de zlib-rs, para que un # consumidor que hoy pide `-lz` y `#include ` no tenga que cambiar una línea. El `.so.1` lleva # el enlace `libz.so` porque es el que busca el enlazador al ver `-lz`. install = ''' mkdir -p /out/usr/lib /out/usr/include /out/usr/lib/pkgconfig cp target/release/libz_rs.a /out/usr/lib/libz.a cp target/release/libz_rs.so /out/usr/lib/libz.so.1 ln -sf libz.so.1 /out/usr/lib/libz.so cp libz-rs-sys/include/zlib.h /out/usr/include/zlib.h cp libz-rs-sys/include/zconf.h /out/usr/include/zconf.h # El .pc con el nombre de zlib: los `configure` del corpus preguntan por `zlib`, no por `zlib-rs`. # `Libs.private: -lunwind` es la parte que NO es cosmética: sin el unwinder el enlace estático muere. cat > /out/usr/lib/pkgconfig/zlib.pc <<'PC' prefix=/usr libdir=${prefix}/lib includedir=${prefix}/include Name: zlib Description: zlib compression library (zlib-rs, Rust) Version: 1.3.0 Libs: -L${libdir} -lz Libs.private: -lunwind Cflags: -I${includedir} PC ''' [deps] build = ["binutils"]