Skip to content

The Ecosystem: crates.io and Three Paradigms ​

Last chapter you took apart the skeleton of a Cargo project. This one looks outward: where do the names in [dependencies] come from, how do you pick one, and how does "what kind of program this wants to be" decide which crates you pull in? By the end you can answer:

  • Given the dependency section of jackdaw/Cargo.toml, say what each crate does, in one sentence apiece.
  • When the AI suggests a crate you've never heard of, know which three sites to check and which three signals to read.
  • When you hear "no_std" or "cross-compilation", place the project in one of three paradigms and say what that means for its dependency list.

Looking up crates: crates.io, docs.rs, lib.rs ​

Rust dependencies need no manual downloading: write a name and a version requirement in Cargo.toml, and Cargo fetches it from crates.io — the official registry, Rust's counterpart to npm. One mismatch in the analogy: npm lets you unpublish a version within 72 hours; on crates.io, a published version is never deleted — the reproducible builds your Cargo.lock promises rest on that immutability (Chapter 8). The publishing unit is a package; the compilation unit is a crate. jackdaw, which you dissected in Chapter 8, is one package holding both a library crate and a binary crate.

Three lookup points, each with a job: crates.io for version history, download counts, and repository links; docs.rs for the API docs generated automatically for every published version — the main battlefield of a read-first course; lib.rs, a third-party index that gathers the scattered quality signals onto one page. To judge whether a crate is safe to use, read three signals: maintenance (last release last week, or three years ago; do issues get answers?), downloads (total for scale, recent for activity), and MSRV (the crate's minimum supported Rust version — too new, and it refuses your toolchain). High downloads don't mean it fits you. Read the docs first, then the numbers.

TIP

How to read docs.rs: scan the types and function signatures first, then the examples at the top. The build compiles doc examples before publish, so broken ones don't survive. When you ask the AI to write code, paste a docs.rs link — it beats a tutorial.

jackdaw's thirteen crates: one line, one reason ​

toml
# ...
# Dependencies are kept to the minimum the blueprint lists: nothing here is
# "nice to have" — each crate appears because a spec behavior needs it.
[dependencies]
tokio = { version = "1", features = ["full"] }
axum = "0.8"
reqwest = { version = "0.12", features = ["json", "stream"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.9"
clap = { version = "4", features = ["derive"] }
thiserror = "2"
anyhow = "1"
async-trait = "0.1"
futures-util = "0.3"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

How to read it: start at the tokio line. Each entry is "name = version requirement, plus optional features". The requirement isn't an exact version: "1" means any 1.x, "0.8" means 0.8.x — the semantic versioning rules from Chapter 8. A feature is the crate's switch panel: reqwest turns on only json and stream, because jackdaw only sends JSON requests and only receives streaming responses — features you don't name never get compiled. Chapter 8 walked only the first nine lines of this list (that's where its # ... cut); the last four are completed here, full picture in view. The two comment lines right above [dependencies] you also met in Chapter 8 — the author's own defense: every crate exists because a spec behavior needs it; none is a "might be useful later".

Thirteen crates, one line each:

  • tokio — the async runtime: the scheduler that drives every .await (Chapter 7);
  • axum — the HTTP server framework: the gateway listens and dispatches through it (Chapter 14);
  • reqwest — the HTTP client: what knocks on the provider's door (Chapter 12);
  • serde — the serialization framework: the translation rules between data structures and file formats (first seen in Chapter 6);
  • serde_json — serde's JSON backend: the format of chat requests and responses;
  • toml — serde's TOML backend: reads jackdaw.toml (Chapter 11);
  • clap — command-line argument parsing: subcommands and flags (Chapter 10);
  • thiserror — macros for library error types: one variant per line, exhaustively matchable;
  • anyhow — the error container for the binary side: holds anything, lets ? climb all the way up;
  • async-trait — lets a trait declare async fn (why you need it: Chapter 7);
  • futures-util — the async toolbox, mostly its stream adapters for reading SSE chunk by chunk (Chapter 12);
  • tracing / tracing-subscriber — the structured-logging pair: the former adds the instrumentation points, the latter prints to the terminal and filters by environment variable.

Three paradigms: how the program lives ​

Ask one question first about any Rust project: how does this program live? Three answers, three paradigms.

Backend services (axum + tokio): a long-lived process that starts and never exits, living inside a request loop. The concerns match a Node or Go service: connection pooling (reuse upstream connections instead of handshaking per request) and graceful shutdown (on a termination signal, stop accepting, finish the requests in flight, then exit).

Desktop and CLI applications (clap, Tauri): short-lived processes that run to completion and exit. Their outward interface is the exit code (0 for success, non-zero for failure — shell scripts branch on it) and their output; they care about startup speed and install size. Tauri extends this line to the desktop: package a frontend with the system WebView instead of shipping a whole browser.

Systems-level libraries (no_std, FFI, embedded): no process at all. Something else links them into its firmware or program, in an environment that may lack an operating system and a heap allocator — no_std means building without the standard library, where even String and Vec are off by default. The exit is FFI, the foreign function interface, for C to call.

Backend serviceDesktop & CLISystems library
LifetimeLong-lived, request loopRun and exitNo process
InterfaceHTTP, streaming responsesExit code, stdoutC ABI
Typical depsaxum, tokioclap, Taurino_std, no runtime

A paradigm isn't taste; it's a hard constraint that prunes the dependency list from the opposite direction: a no_std firmware cannot depend on tokio — a runtime wants threads, a network stack, and a heap, and the firmware may have none of them. jackdaw straddles the first two: the command-line entry (clap) is a CLI, and the gateway that serve starts is a long-lived service — which is why clap and axum both appear in the list. Read the dependencies, and you can guess which side of the line a project stands on.

Shipping: release profile and cross-compilation ​

Chapter 8 showed you target/debug and target/release: one project, two postures — dev compiles fast, release optimizes hard. The release profile is the control panel for that trade-off. jackdaw asks two things of release:

toml
# ...
# Shipping profile: small binaries beat last-percent compile speed for a
# teaching project you build once and copy around.
[profile.release]
lto = true
strip = true

How to read it: this comment and this [profile.release] are the same ones Chapter 8's shipping section read — the trade is already on record: built once, copied around, a small binary beats fast compiles. Only the new detail matters here. lto = true turns on link-time optimization, letting the compiler inline across crates: slower builds, smaller and faster binaries. strip = true drops the symbol table (the name index for functions and variables): smaller still, at the price of unreadable crash backtraces. Whenever you read a [profile.*] section, it answers one question: "what did the author tell the compiler to optimize for?"

Cross-compilation is the last piece of the map: produce, on your MacBook, a binary that runs on a Linux server — rustup target add the target platform, cargo build --release --target <triple> to aim at it. This is built into the toolchain, nothing bolted on. Simplified here: a pure-Rust target is one command away; a target that needs a C linker takes one more step (Chapter 15) — for this course the concept is enough.

Summary ​

  • Three lookup points: crates.io (metadata), docs.rs (API docs), lib.rs (aggregated signals); three trust signals: maintenance, downloads, MSRV.
  • jackdaw's thirteen crates by role: the runtime, both ends of HTTP, the serialization trio, the CLI, the error pair, async glue, the logging pair.
  • Three paradigms by how the program lives: long-lived service, run-and-exit application, process-less library. The paradigm decides which dependencies can't enter at all.
  • The release profile tunes the artifact (lto/strip); cross-compilation produces another platform's binary from one machine.

Next chapter opens Part 4: jackdaw grows from an empty directory, and the first cut is the CLI that speaks.

Self-Check ​

  1. Migration: in Python you used requests for HTTP, argparse for the command line, and the standard library json for JSON. What replaces each in Rust? Which answer surprises you most compared to Python?

  2. Prediction: the AI proposes "bring the optimizations to everyday development too" and hands you this diff. If you accept it, what happens to your daily cargo run?

    diff
     [profile.release]
     lto = true
     strip = true
    +[profile.dev]
    +lto = true
    +strip = true
  3. Explanation: a colleague wants to compile jackdaw straight into a no_std sensor firmware as a built-in gateway. Using only the dependency list above, where does this path break, and why?

Answers
  1. requests → reqwest; argparse → clap; json → serde + serde_json. The surprise is the third: Rust's standard library stays deliberately small, so even JSON comes from crates.io — that's the ecosystem's default division of labor, not a gap.
  2. dev exists to compile fast and debug well. lto slows every cargo build and cargo run (incremental builds included), and strip leaves crash backtraces as bare addresses. You end up with a daily toolchain that is slower and harder to debug — those switches belong in release.
  3. It breaks on tokio, axum, and reqwest — the whole chain: a runtime wants threads, a network stack, and a heap allocator, and the firmware may have none of them; under no_std even String and Vec are off by default. The workable move is to leave jackdaw on the side with an operating system and let the firmware call it over the network — the paradigm boundary is the dependency-list boundary.