Hedronite Lesson · Polyglot-Dev / Rust · Thu 2026-09-24

Cargo, workspaces, crates.io

Read the manifest. Pin the version. Share one lockfile across two crates.

Lesson Class: Duha (Rust language track)
Focus: Cargo.toml read · version pin · two-crate workspace · profiles
Code Blocks: clean blocks, explanation in prose
Done-criteria: Read Cargo.toml + pin a version + sketch two-crate workspace
Grounding: TRPL stable ch14-00..03 · install/extend named only · Blandy not cited
The manifest
Package identity, profiles, and dependency pins live in one file.
The pin
crate = "x.y.z" is a requirement; package version is what you publish.
The workspace
Shared Cargo.lock and target/; path dep wires binary to lib.
Read the manifest. Pin the version. Share one lockfile across two crates.

<!-- hal:authoritative:yaml -->

Read the manifest. Pin the version. Share one lockfile across two crates.

§I - Frame

Duha session 16. Topics #16 after the 2026-09-23 ch13-03 interstitial left Cargo unmarked. Today is TRPL Chapter 14: more about Cargo and crates.io. You already build, run, and test with Cargo. Now you customize profiles, read a publishable Cargo.toml, and sketch a workspace.

Three moves land by the end:

  1. Read the manifest: [package], [profile.*], and [dependencies] tell you what the crate is, how it builds, and what it pins.
  2. Pin a version: a dependency string like rand = "0.8.5" (and the package version field) are deliberate, not defaults you ignore.
  3. Sketch a workspace: a root [workspace] with two members, one path dependency, one shared Cargo.lock and target/.

Done-criteria: Can read a Cargo.toml, pin a version, and sketch a two-crate workspace.

Do not publish to crates.io from this lesson. Do not open smart pointers (Ch.15).

§II - Profiles: dev vs release

Cargo picks a release profile when you compile. cargo build uses dev. cargo build --release uses release. The finished line names the profile:

$ cargo build
    Finished `dev` profile [unoptimized + debuginfo] ...
$ cargo build --release
    Finished `release` profile [optimized] ...

Defaults live in Cargo until you override them. opt-level is the usual lever (0 through 3). Development wants fast compile; release wants fast run:

# Cargo.toml — defaults if you omit the sections
[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3

Override only what you need. Setting opt-level = 1 under [profile.dev] keeps every other dev default and raises optimization one notch for local builds. Profiles are independent: changing dev does not touch release.

§III - Read Cargo.toml; pin a version

A crate you mean to share needs [package] metadata. Name must be unique on crates.io. version is the package version you publish. description and license are required before cargo publish succeeds:

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2024"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"

[dependencies]

SPDX identifiers fill license (MIT, Apache-2.0, or MIT OR Apache-2.0). A missing description or license fails publish with a 400 from the registry. Publish is permanent for that version: you cannot overwrite it; you publish a new version instead.

Pinning a dependency is the other version move. In a member crate:

[dependencies]
rand = "0.8.5"

That string is a Cargo version requirement. Cargo resolves it once into the workspace Cargo.lock. Reading a manifest means you can say what the package version is, what each dependency string requires, and whether profiles override defaults. You do not need to memorize every metadata key; you need to find name, version, description, license, and the dependency pins.

Doc comments (///, crate-level //!) and cargo doc --open belong to the publish chapter. Treat them as the public face of the same crate; skip rustdoc HTML layout here.

§IV - Two-crate workspace

A workspace is a set of packages that share one Cargo.lock and one target/ directory. Root Cargo.toml has no [package] section. It starts with [workspace]:

[workspace]
resolver = "3"
members = ["adder", "add_one"]

cargo new adder inside the workspace root creates the binary and appends it to members. cargo new add_one --lib adds the library the same way. Layout:

add/
├── Cargo.lock
├── Cargo.toml          # workspace root
├── adder/
│   ├── Cargo.toml
│   └── src/main.rs
├── add_one/
│   ├── Cargo.toml
│   └── src/lib.rs
└── target/             # shared

Cargo does not assume members depend on each other. Wire the path explicitly in adder/Cargo.toml:

[dependencies]
add_one = { path = "../add_one" }

Library:

// add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
    x + 1
}

Binary:

// adder/src/main.rs
fn main() {
    let num = 10;
    println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}

Build from the workspace root. Run one package with -p:

$ cargo build
$ cargo run -p adder

One lockfile keeps every member on the same resolved versions when both list rand = "0.8.5". Shared target/ avoids rebuilding the same dependency once per member.

Brief chapter edges (name only): cargo install puts a binary crate into ~/.cargo/bin; a binary named cargo-something on your PATH shows up as cargo something. Neither is required for Done-criteria.

§V - One complete proof

  1. Point at [package], [profile.dev] (or say the default), and [dependencies] in a real Cargo.toml and name what each block controls.
  2. Write a dependency pin (crate = "x.y.z") and say how it differs from the package version field.
  3. Sketch a workspace root with members = ["adder", "add_one"], a path dep from binary to lib, and say why Cargo.lock and target/ sit at the root.
  4. Name cargo run -p adder as the way to run one member from the root.
  5. Say why you will not run cargo publish from this lesson (permanence / out of scope).

When those five hold, session 16's selected depth is done. Smart pointers (Ch.15) wait for the next Topics row.

§VI - Closing

Cargo is more than build and test. Profiles trade compile time for run time. The manifest holds identity, license, and version pins. A workspace shares lock and target across crates you develop together. Read the file. Pin the version. Sketch the two-crate layout. Next TRPL depth is smart pointers when the syllabus seats it.

Done-criteria: Can read a Cargo.toml, pin a version, and sketch a two-crate workspace.

Related