wandres.dev
PUBLICAR Y ECOSISTEMA · crates.io, docs.rs

Publicar un crate en crates.io

cargo publish es una sola orden, pero también un acto irreversible: lo que subes al registro queda ahí para siempre. Los metadatos de [package] que crates.io exige y recomienda, el flujo token → package → publish con --dry-run como red, qué ficheros entran en el tarball, y por qué yank no borra sino que desalienta.

⏱ 20 min

Hasta ahora tu crate vivía en tu disco, resoluble solo por una path en el Cargo.toml de un colega. Publicarlo en crates.io lo convierte en algo distinto: infraestructura compartida que cualquiera en el planeta puede añadir con cargo add. El gesto técnico es minúsculo —una orden, cargo publish— pero su naturaleza es grave: subir una versión al registro es irreversible. No hay botón de deshacer, no puedes reemplazar un 0.1.0 defectuoso por otro «mejor» con el mismo número, no puedes retirar del todo lo que ya está publicado. Esa permanencia no es un descuido del diseño: es el cimiento sobre el que descansa la reproducibilidad de todo el ecosistema. Aprender a publicar bien es, ante todo, aprender a tratar cada version como una promesa que firmas para siempre.

🎯 Al terminar esta lección sabrás
  • Rellenar los metadatos que crates.io exige y recomienda en [package]: license, description, repository y compañía.
  • Autenticarte con un token y recorrer el flujo packagepublish, usando --dry-run como red de seguridad.
  • Controlar exactamente qué ficheros entran en el .crate con include y exclude.
  • Entender la inmutabilidad del registro y usar cargo yank sabiendo que desalienta, pero no borra.

Los metadatos: la ficha pública de tu crate

Antes de publicar, crates.io exige una ficha mínima. El manifiesto que te bastaba en local se queda corto: sin description ni license el registro rechaza la subida. Estos son los campos que gobiernan cómo el mundo descubre y usa tu crate:

[package]
name = "mi_crate"
version = "0.1.0"
edition = "2024"
description = "Un parser de configuracion tolerante a errores"
license = "MIT OR Apache-2.0"
repository = "https://github.com/tu/mi_crate"
documentation = "https://docs.rs/mi_crate"
homepage = "https://mi_crate.rs"
readme = "README.md"
keywords = ["config", "parser", "toml"]     # maximo 5
categories = ["config", "parsing"]          # de la lista fija de crates.io
rust-version = "1.85"                        # tu MSRV
  • description y license son obligatorios para publicar. El resto es opcional pero esperado: omitirlos es señal de descuido.
  • license es una expresión SPDX, no texto libre: MIT, Apache-2.0, GPL-3.0-or-later. Para una licencia no estándar existe license-file.
  • keywords (hasta cinco) y categories (que deben coincidir con los slugs fijos de crates.io) alimentan la búsqueda y la navegación del registro.
  • documentation apunta por defecto a docs.rs, que construirá tu doc sin que hagas nada —tema de la próxima lección—.
  • rust-version declara tu versión mínima de compilador y el resolvedor de la edición 2024 la respeta al elegir dependencias.
ℹ️
La convención MIT OR Apache-2.0

Casi todo el ecosistema, empezando por el propio compilador y la biblioteca estándar, se publica bajo licencia doble MIT OR Apache-2.0. El motivo es deliberado: Apache-2.0 incluye una concesión explícita de patentes que protege a los usuarios, mientras que MIT es maximalmente simple y compatible con casi todo. Ofrecer ambas con el operador OR deja que quien te consuma elija la que encaje en su proyecto. Adoptar esta convención no es obligatorio, pero desviarse de ella sin razón es fricción gratuita para tus futuros usuarios.

El proceso: token, empaquetado y publicación

Publicar exige una cuenta en crates.io —se crea entrando con GitHub y verificando un correo— y un token de API. El token se pega una vez y cargo lo guarda en ~/.cargo/credentials.toml:

cargo login                  # pega el token de crates.io (guardado local)
cargo package                # empaqueta el .crate y verifica que compila aislado
cargo publish --dry-run      # ensaya todo el flujo SIN subir nada
cargo publish                # el acto irreversible: sube la version al registro

El paso clave antes de publicar es cargo package: construye el archivo .crate —un tarball comprimido con tu código fuente y el manifiesto— y, crucialmente, lo compila de forma aislada en un directorio temporal. Esto atrapa el error clásico: que tu crate solo compilase gracias a un fichero que git ignoraba y que jamás llegaría al usuario. cargo publish --dry-run ejecuta el flujo entero salvo la subida final, tu último ensayo con red.

Cuando por fin corres cargo publish, cargo sube el tarball, el registro lo almacena de forma permanente, actualiza el índice —para que el resolvedor de cualquiera lo encuentre— y docs.rs toma nota para construir la documentación. El registro no compila tu código: solo custodia la fuente.

💡
Trusted Publishing: CI sin tokens de larga vida

Guardar un token de publicación en los secretos de CI es un riesgo: si se filtra, alguien puede publicar en tu nombre. crates.io soporta hoy Trusted Publishing vía OIDC: configuras en el registro qué repositorio y flujo de GitHub Actions tienen permiso para publicar, y el CI obtiene un token efímero de un solo uso en cada ejecución. No hay secreto de larga vida que filtrar. Es la vía recomendada para automatizar release desde 2025.

Qué entra en el tarball: include y exclude

Por defecto, cargo package incluye todo lo versionado por git salvo target/. Casi siempre eso es de más: fixtures de test pesados, imágenes del README, datos de benchmark. Dos listas de patrones te dan control fino:

[package]
exclude = ["tests/fixtures/*.bin", "assets/raw/"]
# o, de forma exhaustiva, solo lo imprescindible:
include = ["src/**/*.rs", "Cargo.toml", "README.md", "LICENSE-*"]

include gana a exclude si defines ambos, y es la opción más segura: describes con precisión qué se publica en vez de perseguir lo que no. Vigila el tamaño del .crate: crates.io impone un límite (del orden de decenas de MiB) y un tarball hinchado ralentiza cada cargo build de tus usuarios, que descargan la fuente entera.

Inmutabilidad: yank no es borrar

Aquí está la regla que define el registro. Una vez publicada, una version es permanente: no puedes sobrescribir 1.0.3, no puedes reciclar un número. Y los nombres son de quien llega primero —el name squatting es real—. Lo máximo que ofrece el registro es cargo yank:

cargo yank --version 1.2.3            # marca esa version como retirada
cargo yank --version 1.2.3 --undo     # revierte el yank

Yank no elimina nada: la versión sigue descargable. Lo que hace es impedir que nuevas resoluciones la elijan. Un Cargo.lock que ya la fijó seguirá compilando —no rompes a quien ya dependía de ella—, pero nadie la seleccionará de cero. Es la herramienta para decir «esta versión tiene un fallo grave, no la uséis en proyectos nuevos» sin arrancarle el suelo a los que ya la tienen anclada. El borrado real solo ocurre en casos excepcionales —malware, problemas legales— y lo ejecuta el equipo de crates.io, no tú.

flowchart TD
A[cargo package empaqueta y compila aislado] --> B[cargo publish dry-run ensaya sin subir]
B --> C[cargo publish sube el tarball]
C --> D[El registro almacena la version PARA SIEMPRE]
D --> E[El indice se actualiza y queda resoluble]
D --> F[docs.rs construye la documentacion]
D -.->|yank no borra| G[Version retirada solo para nuevas resoluciones]
style C fill:#cba6f7,color:#11111b
style D fill:#f38ba8,color:#11111b
style E fill:#a6e3a1,color:#11111b
style F fill:#89b4fa,color:#11111b
La permanencia es una función, no una rigidez: sostiene la reproducibilidad de un ecosistema entero

Detente en por qué crates.io se niega a dejarte borrar. Parece una limitación, casi una crueldad —publicaste un fallo y no puedes quitarlo—, pero es exactamente lo contrario: es la propiedad que hace posible que un árbol de quinientas dependencias transitivas compile hoy igual que dentro de una década. Un Cargo.lock fija versiones exactas con su checksum; esa fijación solo vale algo si las versiones que nombra existirán siempre y serán idénticas byte a byte. El día que un autor pudiera borrar o reemplazar una versión publicada, cada lockfile del mundo que la mencionara se volvería una bomba de relojería, y la reproducibilidad —el logro más silencioso de Cargo— se evaporaría. La historia ya escribió la lección en otro ecosistema: cuando un autor retiró de golpe un paquete diminuto llamado left-pad, medio internet dejó de construir en una tarde, porque allí borrar sí borraba. crates.io diseñó su registro para que ese incidente sea estructuralmente imposible. Y por eso yank es tan sutilmente correcto: no borra, porque borrar traicionaría a quien ya depende de ti; solo desalienta, redirigiendo las futuras elecciones sin romper las pasadas. La inmutabilidad convierte cada publicación en un compromiso serio —piensa dos veces antes de subir— pero a cambio te entrega algo que casi ningún ecosistema tiene: la certeza de que lo que compiló una vez, compilará siempre. No es una atadura sobre el autor; es una garantía para todos los demás, y tú eres «todos los demás» en cada crate que consumes.

📝
Lo esencial de publicar

crates.io exige description y license (expresión SPDX; la convención es MIT OR Apache-2.0) y recomienda repository, keywords y categories. El flujo es cargo logincargo package (empaqueta y compila aislado) → cargo publish --dry-runcargo publish. Controla el contenido del .crate con include/exclude. Toda versión publicada es permanente e irreversible; cargo yank no borra, solo impide que nuevas resoluciones la elijan sin romper los Cargo.lock existentes. En CI, prefiere Trusted Publishing (OIDC) a un token de larga vida.

⚔️ Publica de mentira, aprende de verdad
  1. Completa el [package] de un crate con description, license = "MIT OR Apache-2.0", repository, keywords y categories. Corre cargo publish --dry-run y lee cada advertencia.
  2. Ejecuta cargo package y descomprime el .crate resultante: lista qué ficheros incluyó Cargo y cuáles dejó fuera.
  3. Añade un tests/fixtures/ con un archivo pesado, comprueba que aparece en el tarball, y elimínalo con exclude. Verifica el cambio de tamaño.
  4. Explica con tus palabras por qué cargo yank no rompe a un usuario cuyo Cargo.lock ya fija la versión retirada, pero sí impide un proyecto nuevo.
  5. Argumenta qué desastre concreto evitó crates.io al hacer las versiones inmutables, usando el caso left-pad como contraste.