External Crates and Cargo.toml

Rust's standard library is deliberately small. It gives you the core language and data structures, but it does not include things like random number generation, graphics, or HTTP clients. For everything else, you reach for crates — the name Rust uses for packages.

The ecosystem is large and well-organized. There is a crate for almost anything you need, and adding one takes a single line in your project file.

Adding a dependency

Open Cargo.toml and add to the [dependencies] section:

[dependencies]
rand   = "0.8"
minifb = "0.27"

That is the entire process. The next time you run cargo build or cargo test, Cargo fetches both crates from the internet, compiles them, and links them into your project. No separate install step, no environment variables, no build scripts.

Where crates come from

The official registry is crates.io. When you add a dependency, Cargo looks it up there by default. You can search for crates, read their documentation, and check how actively they are maintained — all on the same site.

Version numbers

Rust uses semantic versioning: major.minor.patch. The version string in Cargo.toml is a constraint, not a pin:

SpecifierWhat Cargo accepts
"0.8"anything ≥ 0.8.0 and < 0.9.0
"0.8.5"exactly 0.8.5, nothing else
"^0.8"same as "0.8" — caret is the default

The common form "0.8" says: "give me any compatible update within this minor version." Patch updates (bug fixes) are included; a new minor version that might break things is not.

Cargo.lock

The first time you build, Cargo resolves every dependency — including all the transitive ones that your direct dependencies pull in — and records the exact versions in Cargo.lock. From that point on, every build uses those exact versions.

For a binary project like this emulator, Cargo.lock should be committed to version control. It ensures that every developer, every CI run, and every deployment uses identical code — even months later when newer versions of crates have been released.

Using a crate in code

Two steps: declare it in Cargo.toml, then bring the right items into scope:

#![allow(unused)]
fn main() {
use rand::Rng;  // the Rng trait must be in scope for .gen() to work

let value: u8 = rand::thread_rng().r#gen();
}

Just having a crate as a dependency is not enough. Traits must be explicitly imported for their methods to be available — that is a deliberate design choice in Rust's module system. Without use rand::Rng, the .r#gen() call does not compile, even if the crate is in Cargo.toml.

The r#gen() syntax is a workaround for a keyword conflict introduced in Rust 2024. The full story is in Raw Identifiers.


In the emulator: Two crates are used. rand powers the CXNN opcode that generates random numbers — see RND — Random Numbers. minifb opens the window and renders the framebuffer — see The Main Loop.