wandres.dev
SETUP Y CARGO · rustup, cargo, edición 2024

cargo, el gestor todo-en-uno

cargo new, check, build, run y test: qué hace exactamente cada orden, en qué se diferencian, y cómo Cargo.toml y Cargo.lock gobiernan tus dependencias con semver.

⏱ 15 min

En otros lenguajes juntas a mano un compilador, un gestor de paquetes, un runner de tests y un generador de documentación, cada uno con su cultura y sus rarezas. En Rust todo eso es una sola herramienta: cargo. Aprender sus verbos es aprender el flujo de trabajo entero.

🎯 Al terminar esta lección sabrás
  • Crear paquetes binarios y de librería con cargo new.
  • Dominar el ciclo check, build y run, y cuándo usar cada uno.
  • Ejecutar la batería de pruebas con cargo test.
  • Declarar dependencias en Cargo.toml y entender semver y el lockfile.

Crear un paquete

cargo new hola            # paquete binario (con src/main.rs)
cargo new --lib mi_lib    # paquete de librería (con src/lib.rs)
cargo init                # inicializa cargo en un directorio ya existente

cargo new hola genera la estructura mínima y un repositorio git:

hola/
├── Cargo.toml     # el manifiesto: metadatos y dependencias
├── .gitignore     # ignora /target
└── src/
    └── main.rs    # el punto de entrada

El ciclo diario: check, build, run

Estas tres órdenes son el 90% de tu día, y confundirlas cuesta tiempo:

flowchart TD
S[codigo y Cargo.toml] --> C[cargo check verifica tipos]
S --> B[cargo build genera binario]
B --> R[cargo run ejecuta]
B --> T[cargo test compila y prueba]
  • cargo check analiza y comprueba tipos y borrow checker, pero no genera el binario final. Es varias veces más rápido que build: es tu bucle de feedback mientras escribes.
  • cargo build compila de verdad. Sin flags, produce un binario debug en target/debug/ (rápido de compilar, sin optimizar, con símbolos). Con --release produce uno optimizado en target/release/ (lento de compilar, veloz de ejecutar).
  • cargo run hace build si hace falta y ejecuta el binario. Acepta los mismos flags: cargo run --release.
cargo check                  # el más rápido: solo valida
cargo build                  # binario debug
cargo build --release        # binario optimizado
cargo run -- arg1 arg2       # ejecuta; lo que va tras -- son args del programa
💡
check es tu compañero de escritura

Deja cargo check (o rust-analyzer, que lo hace por ti) corriendo mientras programas. Como omite la generación de código máquina, te da los errores de tipos y de ownership en una fracción del tiempo de un build completo. Reserva build --release para medir rendimiento o publicar.

Probar: cargo test

cargo test compila un harness especial y ejecuta todo lo que sea una prueba: funciones marcadas con #[test], los tests de integración en tests/ y hasta los ejemplos de tu documentación.

pub fn suma(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn suma_positivos() {
        assert_eq!(suma(2, 3), 5);
    }
}
cargo test                 # toda la batería
cargo test suma_positivos  # solo los tests cuyo nombre contenga eso
cargo test -- --nocapture  # deja ver los println de los tests

El #[cfg(test)] hace que ese módulo solo se compile al probar: no pesa ni un byte en tu binario final.

Cargo.toml y las dependencias

El manifiesto declara qué es tu paquete y de qué depende:

[package]
name = "hola"
version = "0.1.0"
edition = "2024"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
rand = "0.9"

[dev-dependencies]
criterion = "0.5"   # solo para tests y benchmarks

La forma más cómoda de añadir dependencias es dejar que cargo edite el manifiesto:

cargo add serde --features derive
cargo add tokio --features full

Una versión como "1.0" es, por defecto, un requisito caret: equivale a >=1.0.0, <2.0.0. Cargo elige la versión más alta compatible y la congela en Cargo.lock, con hashes y versiones exactas de todo el árbol de dependencias.

Más allá de las dependencias, el manifiesto controla cómo se compila. Los perfiles ajustan la optimización, y un workspace agrupa varios crates que comparten Cargo.lock y el directorio target/:

[profile.release]
opt-level = 3       # optimización agresiva
lto = true          # link-time optimization entre crates
codegen-units = 1   # compila más lento, binario más rápido

[workspace]
members = ["app", "core", "cli"]
ℹ️
Cargo.lock: cuándo se versiona

Para una aplicación (un binario), commitea Cargo.lock: garantiza que todos compilan bits idénticos. Para una librería el criterio clásico era no versionarlo, aunque hoy se recomienda hacerlo también para que tu CI sea reproducible. cargo update recalcula el lockfile dentro de los rangos de semver.

cargo es por qué el ecosistema de Rust es coherente

Cargo no es un lujo: es una de las razones por las que Rust se adopta tan rápido. Un manifiesto declarativo, un resolvedor de dependencias con semver de verdad, un lockfile reproducible, un runner de tests integrado, doctests que garantizan que tus ejemplos compilan, y cargo doc, cargo bench, cargo tree y cargo add, todo con la misma interfaz y sin configuración. Compáralo con el mundo de C, donde el build system, el gestor de paquetes y el runner de tests son decisiones separadas que cada proyecto resuelve a su manera. La consecuencia es cultural: como todos usan cargo, cualquier crate de crates.io se compila con cargo build sin leer instrucciones. Esa uniformidad —una sola herramienta que todos comparten— es un activo de ingeniería tan valioso como el propio borrow checker. Dominar sus verbos no es aprender un comando: es adoptar el flujo de trabajo estándar de todo un ecosistema.

⚔️ Ejercita todos los verbos
  1. Crea un paquete binario con cargo new e inspecciona el Cargo.toml y el árbol de archivos.
  2. Lanza cargo check, cargo build y cargo build --release, y compara qué aparece en target/.
  3. Añade rand con cargo add y úsalo para imprimir un número aleatorio con cargo run.
  4. Escribe una función con un #[test] y córrelo con cargo test.
  5. Mira el árbol de dependencias con cargo tree y abre la doc de tu crate con cargo doc --open.