Un paquete, dos entidades: ¿mito o realidad?
\n\nImagina: has escrito en Rust una biblioteca increíble para trabajar con grandes datos. Es ultrarrápida, segura y elegante. Pero no solo necesitas una API para otros desarrolladores, sino también una utilidad CLI lista para probar la biblioteca o usarla en pipelines. Surge la pregunta clásica que ha acumulado miles de visitas en Stack Overflow: «Package with both a library and a binary?» (paquete que contiene tanto una biblioteca como un archivo ejecutable).
\n\nPara un principiante en Rust, esto suena como un rompecabezas. Para un desarrollador experimentado, es una tarea de optimización de la estructura del proyecto y el rendimiento. En este artículo no solo responderemos a esta pregunta, sino que lo haremos con un enfoque en optimización y rendimiento — las dos cosas por las que Rust es más amado.
\n\nAnalizaremos cómo organizar correctamente dicho paquete, evitar la duplicación de código, acelerar la compilación y hacer que tu binario sea realmente rápido. ¡Allá vamos!
\n\nEstructura del paquete: Cargo.toml lo resuelve todo
\n\nEn Rust, un solo paquete Cargo puede contener tanto una biblioteca (lib) como varios binarios (bin). Esto no es magia, sino una simple convención sobre la estructura de carpetas. Veamos un ejemplo mínimo.
\n\nCreemos un proyecto:
\n\ncargo new my_tool --lib\n\nAhora modifiquemos el archivo Cargo.toml para indicar explícitamente que tenemos tanto una biblioteca como un binario:
[package]\nname = "my_tool"\nversion = "0.1.0"\nedition = "2021"\n\n[lib]\nname = "my_tool_lib"\npath = "src/lib.rs"\n\n[[bin]]\nname = "my_tool"\npath = "src/main.rs"\n\n[dependencies]\n\n\nLa estructura de carpetas se verá así:
\n\nmy_tool/\n├── Cargo.toml\n├── src/\n│ ├── lib.rs // Biblioteca\n│ └── main.rs // Binario\n\n\nEs sencillo, ¿verdad? Pero el diablo está en los detalles. Si simplemente copias el código de la biblioteca en main.rs, obtendrás duplicación y problemas de rendimiento. Hay que actuar con más inteligencia.
\n\nEnfoque correcto: el binario usa la biblioteca
\n\nEl binario debe importar la biblioteca como un crate externo. Incluso si están en el mismo paquete. Esto no solo reduce la duplicación, sino que también permite al compilador de Rust optimizar mejor el código mediante optimización entre crates (LTO).
\n\nEjemplo de src/lib.rs:
pub fn calculate(data: &[u64]) -> u64 {\n data.iter().sum()\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_calculate() {\n assert_eq!(calculate(&[1, 2, 3]), 6);\n }\n}\n\n\nEjemplo de src/main.rs:
use my_tool_lib::calculate;\n\nfn main() {\n let numbers = vec![10, 20, 30];\n println!("Suma: {}", calculate(&numbers));\n}\n\n\nObserva: usamos my_tool_lib — el nombre que asignamos a la biblioteca en Cargo.toml. El compilador las vinculará automáticamente en un solo binario, pero manteniendo la modularidad.
Optimización del rendimiento: LTO, inline y benchmarks
\n\nAhora que la estructura está lista, hablemos de lo principal: cómo exprimir al máximo tu paquete. Porque si estás creando una herramienta para procesar datos, cada microsegundo cuenta.
\n\n1. Activar la optimización en tiempo de enlace (LTO)
\n\nLTO permite al compilador optimizar el código a través de los límites de los crates. Sin LTO, las funciones de la biblioteca y el binario se optimizan por separado. Con LTO, como una sola unidad.
\n\nAñade en Cargo.toml:
[profile.release]\nlto = true\ncodegen-units = 1\n\n\nEsto aumentará el tiempo de compilación, pero hará que el binario sea notablemente más rápido. Para el sitio educativo pythonlib.ru es importante destacar: LTO es el estándar para compilaciones de producción en Rust.
\n\n2. Usar sugerencias inline
\n\nSi en la biblioteca hay funciones críticas,