Project shape
Before reading Rust, know where Rust code lives. Every Rust project is a Cargo project. Every Cargo project follows a small, predictable shape. Once you know it, navigation becomes trivial.
The files that always exist
my-project/
├── Cargo.toml # The manifest. Dependencies, metadata, build config.
├── Cargo.lock # Resolved exact versions. Committed for apps, ignored for libs.
├── src/
│ ├── main.rs # Binary entry point. App projects.
│ └── lib.rs # Library entry point. Library projects.
└── target/ # Build artifacts. Gitignored.A minimal project has just Cargo.toml and src/main.rs (or src/lib.rs). Add more files as you grow.
Cargo.toml: the contract
[package]
name = "my-project"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"
[dev-dependencies]
proptest = "1"Three sections you will see constantly:
[package]: name, version, edition (use2021or2024), license, MSRV.[dependencies]: production deps with optional features.[dev-dependencies]: deps used only in tests and benchmarks.
The agent will modify Cargo.toml. Read every diff to it.
Bigger projects: workspaces
For projects with multiple crates, you get a workspace:
my-workspace/
├── Cargo.toml # Workspace manifest.
├── crates/
│ ├── foo/
│ │ ├── Cargo.toml
│ │ └── src/lib.rs
│ └── bar/
│ ├── Cargo.toml
│ └── src/lib.rs
└── target/ # Shared across all crates.The root Cargo.toml lists [workspace] members = ["crates/*"]. Sail is a workspace of ~36 crates organized this way. The pattern scales.
Where code goes inside src/
src/
├── lib.rs # Public surface. Mostly `mod` and `pub use`.
├── error.rs # Error enum for this crate. Almost always present.
├── config.rs # Config struct, if applicable.
├── server/
│ ├── mod.rs # Module entry. `mod` declarations.
│ ├── handler.rs # Submodule.
│ └── middleware.rs
└── tests/ # NOT here. Tests live elsewhere.The pattern: one file = one module. A subdirectory with mod.rs is a multi-file module. Use pub mod foo; in the parent to expose it.
Tests live in three places: inline #[cfg(test)] mod tests blocks, in tests/ at the crate root (integration tests), and in benches/ (benchmarks).
The commands that make you dangerous
cargo new my-project --bin # New binary project
cargo new my-lib --lib # New library
cargo build # Compile (debug profile)
cargo build --release # Compile (release profile, slower compile, faster runtime)
cargo run # Compile and run the binary
cargo run -- arg1 arg2 # Pass args to your binary after the --
cargo test # Run tests
cargo check # Typecheck without producing binary. Fast.
cargo fmt # Format code via rustfmt
cargo clippy # Lint
cargo doc --open # Build docs and open them
cargo update # Update Cargo.lock
cargo add serde --features derive # Add a dependency
cargo remove serde # Remove onecheck, test, clippy, fmt are the four you will run all day. They are also the gates you should require every agent diff to clear.
The files you can ignore for now
The agent might create or touch these, but you do not need to learn them on day one:
build.rs— pre-build scripts (codegen, native lib linking).rust-toolchain.toml— pins the Rust version per project..cargo/config.toml— Cargo's own config (registry, profiles, target tuning).rustfmt.toml,clippy.toml— rustfmt and clippy config.deny.toml—cargo denyconfig.
When the agent adds one of these, ask why. Each has a real purpose, but each also expands the surface you have to maintain.
What "navigating Sail" actually looks like
The Sail repo lives at lakehq/sail on GitHub. To explore it productively:
- Open the root
Cargo.toml. Look atmembersto see the crate list. - Open
crates/sail-common/src/lib.rs. This is the foundational crate; reading it tells you what shared types exist. - Open any crate's
error.rs. Sail's error pattern is canonical. - Use
cargo doc --workspace --opento get a clickable graph of public items.
Two minutes in, you have the shape of a 36-crate workspace.