Cargo and Crates
Managing dependencies, organizing multi-file projects, and publishing your own code to crates.io.
3 min read
You met Cargo early in this course as the tool behind cargo new and cargo run. This lesson covers what it does once a project grows past a single file: managing dependencies, organizing modules, and sharing code as a crate — Rust's term for a compiled package or library.
Adding a dependency
Dependencies go in Cargo.toml under [dependencies]:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
rand = "0.8"Running cargo build or cargo run downloads the crate and everything it depends on from crates.io, Rust's central package registry, and records the exact resolved versions in Cargo.lock. cargo add serde does the same edit to Cargo.toml for you, without needing to look up the current version by hand.
Cargo.lock should be committed for binaries (applications), so every build uses identical dependency versions — but conventionally left out of version control for libraries, so downstream consumers can resolve versions against their own constraints.
Semantic versioning
Rust's ecosystem leans heavily on semantic versioning. "1.0" in Cargo.toml actually means "any compatible 1.x release, but not 2.0" — Cargo will happily pick up a patch or minor bump automatically, since semver promises those won't break your code, but never a major version bump without you changing the constraint yourself.
Organizing code into modules
A single-file main.rs doesn't scale. Rust organizes code into a tree of modules, declared with mod:
// src/lib.rs
mod utils;
pub fn run() {
utils::greet();
}// src/utils.rs
pub fn greet() {
println!("Hello from utils!");
}Items are private to their module by default — pub is required to expose a function, struct, or field to code outside it. This mirrors the same "safe by default, opt in to exposure" philosophy you've seen elsewhere in Rust, like immutability by default and single-ownership by default.
Workspaces for multiple crates
A workspace groups several related crates that build together and share one Cargo.lock, useful once a project splits into, say, a core library and a binary that uses it:
# Cargo.toml at the workspace root
[workspace]
members = ["app", "core"]Useful Cargo commands
cargo check # type-check without producing a binary — much faster than build
cargo test # run tests (functions marked #[test])
cargo fmt # auto-format code to the standard style
cargo clippy # lint for common mistakes and non-idiomatic patterns
cargo doc --open # build and view documentation for your project and its dependenciescargo check in particular is worth using constantly while writing code — it runs the borrow checker and type checker without the slower step of generating machine code, giving you fast feedback in the exact same edit-check loop other languages use a linter for.
Publishing your own crate
Once a library is ready to share, cargo publish uploads it to crates.io (after cargo login with an API token). Anyone can then add it to their own Cargo.toml by name — the same mechanism that makes serde, tokio, and every other crate you'll use throughout your Rust career available to you in the first place. The ecosystem's strength is largely a product of how low-friction this process is: publishing a crate and depending on one use exactly the same Cargo.toml mechanics you've already learned.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.