Project Anatomy — Cargo.toml, src/, and the Module System
Playground note: Project anatomy inherently involves multiple files and directory structure — it cannot be demonstrated in a single-file browser playground. Run the examples locally with
cargo newandcargo run.
What cargo new gives you
Section titled “What cargo new gives you”When you run cargo new my-app, Cargo creates a minimal but complete project:
flowchart TD root["my-app/"] --> toml["Cargo.toml — project manifest (= package.json)"] root --> lock["Cargo.lock — pinned dependency versions (= package-lock.json)"] root --> src["src/"] src --> main["main.rs — entry point (= src/index.ts)"]
For a library (no main, just an API for other crates to use):
cargo new --lib my-libflowchart TD root["my-lib/"] --> toml["Cargo.toml"] root --> src["src/"] src --> lib["lib.rs — library entry point (= index.ts that only exports)"]
Cargo.toml vs package.json
Section titled “Cargo.toml vs package.json”// package.json{ "name": "my-app", "version": "1.0.0", "description": "A Node.js app", "main": "dist/index.js", "scripts": { "build": "tsc", "start": "node dist/index.js" }, "dependencies": { "express": "^4.18.0" }, "devDependencies": { "typescript": "^5.0.0", "@types/express": "^4.17.0" }}# Cargo.toml[package]name = "my-app"version = "0.1.0"edition = "2021" # Rust edition (like target in tsconfig)
# No separate devDependencies:# [dependencies] = runtime deps# [dev-dependencies] = test-only deps
[dependencies]serde = { version = "1", features = ["derive"] }tokio = { version = "1", features = ["full"] }
[dev-dependencies]pretty_assertions = "1"Key differences:
- There is no
scriptssection. You runcargo run,cargo test, etc. directly. editioncorresponds to the Rust language edition (2015, 2018, 2021). Always use2021for new projects.- Dependencies do not need
@types/packages. Rust types are always part of the crate. - Feature flags (
features = ["derive"]) are Rust’s equivalent of optional peer dependencies or configuration flags. They let crates expose additional APIs that are compiled in only when requested.
src/main.rs and src/lib.rs
Section titled “src/main.rs and src/lib.rs”A binary crate has src/main.rs. A library crate has src/lib.rs. You can have both in the same project — src/lib.rs contains the shared logic, src/main.rs calls into it.
flowchart TD
root["my-app/"] --> src["src/"]
src --> main["main.rs — fn main() { ... }"]
src --> lib["lib.rs — pub fn library_code() { ... }"]
src --> utils["utils.rs — a module (pub mod utils;)"]
src --> models["models/"]
models --> mod["mod.rs — module root (or models.rs in Rust 2018+)"]
models --> user["user.rs — pub struct User { ... }"] In TypeScript you use import/export. In Rust you use mod to declare a module and use to bring items into scope:
// TypeScript ESM// src/utils.tsexport function add(a: number, b: number): number { return a + b;}
// src/main.tsimport { add } from './utils';console.log(add(1, 2));// src/utils.rspub fn add(a: i32, b: i32) -> i32 { a + b}
// src/main.rsmod utils; // declares the module (looks for src/utils.rs)use utils::add;
fn main() { println!("{}", add(1, 2));}The target/ directory
Section titled “The target/ directory”When you run cargo build, Rust compiles everything into target/. This is the equivalent of dist/ plus node_modules/.cache/. You never commit it to version control.
flowchart TD root["target/"] --> debug["debug/ — cargo build (fast compile, no optimisation)"] debug --> dbin["my-app — the executable"] root --> release["release/ — cargo build --release (slower compile, fast binary)"] release --> rbin["my-app"]
Add target/ to your .gitignore. Cargo does this automatically when you run cargo new.
Crates vs node_modules
Section titled “Crates vs node_modules”In JavaScript, npm install downloads packages into node_modules/ inside your project. In Rust, cargo add <crate> downloads packages (called crates) into a global registry cache at ~/.cargo/registry/. Your project’s target/ directory contains compiled artifacts, but the source is shared across all projects on your machine.
The global cache means:
- No per-project duplication of the same crate
cargo cleanremovestarget/but keeps the download cache- Building the same version of a crate twice is nearly instant (already compiled)
Cargo.lock
Section titled “Cargo.lock”Like package-lock.json, Cargo.lock pins the exact versions of every transitive dependency. For binary projects, commit Cargo.lock. For libraries published to crates.io, add it to .gitignore — just like you would not commit package-lock.json in a published npm package.