Rust

Publishing Crates

A practical guide to publishing Rust crates on crates.io, from first publish to version management.

Prerequisites

You need a crates.io account (sign in via GitHub) and an API token:

Terminal
cargo login
# Paste your token from https://crates.io/settings/tokens

Cargo.toml Metadata

crates.io requires certain fields. Here's a minimal example:

Cargo.toml
[package]
name = "my-crate"
version = "0.1.0"
edition = "2024"
authors = ["Your Name <you@example.com>"]
description = "A short description of what your crate does"
license = "MIT"
repository = "https://github.com/you/my-crate"
keywords = ["relevant", "keywords"]
categories = ["science"]
readme = "README.md"
The description and license (or license-file) fields are required for publishing. Without them, cargo publish will fail.

Dry Run First

Always verify before publishing:

Terminal
cargo publish --dry-run

This checks:

  • All required metadata is present
  • The crate builds successfully
  • No files are missing from the package

Publishing

Terminal
cargo publish
Published versions are permanent. You can yank a version (hide it from new dependencies) but never delete it. Double-check your version number before publishing.

Version Management

Follow Semantic Versioning:

ChangeVersion BumpExample
Bug fix, no API changePatch0.6.00.6.1
New feature, backward compatibleMinor0.6.00.7.0
Breaking API changeMajor0.7.01.0.0

For pre-1.0 crates (common in early development), minor bumps signal new features and patch bumps signal fixes. Breaking changes in 0.x are expected.

GitHub Releases

Pair every crates.io publish with a GitHub release for visibility:

Terminal
# Read version from Cargo.toml
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')

# Create release with auto-generated notes
gh release create "$VERSION" \
  --title "v$VERSION" \
  --generate-notes \
  --target main

Clean Working Directory

cargo publish refuses to run with uncommitted changes:

Terminal
# If you see "dirty" errors, ensure Cargo.lock is committed
cargo check  # regenerates Cargo.lock if needed
git add Cargo.lock
git commit -m "update Cargo.lock"
Don't use --allow-dirty. Fix the root cause instead — it's usually a stale Cargo.lock.

Checklist

Before every publish:

  1. All tests pass: cargo test
  2. Docs build clean: cargo doc --no-deps
  3. Version bumped in Cargo.toml
  4. CHANGELOG.md updated (if you maintain one)
  5. Dry run passes: cargo publish --dry-run
  6. Committed and pushed to main
  7. GitHub release created after publish

Yanking Versions

If you publish a broken version:

Terminal
cargo yank --version 0.5.0
# To undo:
cargo yank --version 0.5.0 --undo

Yanked versions still work for existing Cargo.lock files but won't be picked up by new projects.

Multiple Crates Workflow

When managing several related crates, a version-checking script helps catch drift:

Terminal
#!/bin/bash
# Compare local versions against crates.io
for dir in rfconversions touchstone gainlineup linkbudget; do
  LOCAL=$(grep '^version' "$dir/Cargo.toml" | head -1 | sed 's/.*"\(.*\)"/\1/')
  REMOTE=$(cargo search "$dir" --limit 1 2>/dev/null | head -1 | sed 's/.*= "\(.*\)".*/\1/')
  if [ "$LOCAL" != "$REMOTE" ]; then
    echo "⚠️  $dir: local=$LOCAL remote=$REMOTE"
  else
    echo "$dir: $LOCAL (up to date)"
  fi
done

This pattern scales well — run it before release days to see what needs publishing.