How to create new Rust project with Cargo

Use the `cargo new` command to generate a new binary project or `cargo new --lib` for a library.

The scaffold command

You just finished installing Rust. The terminal is open. You have an idea for a tool, but you don't want to spend twenty minutes configuring build scripts, hunting down dependency files, and guessing where the source code should live. You need a working scaffold instantly. Cargo gives you that scaffold with a single command. It sets up the directory structure, writes the configuration file, and provides the entry point. You type one line, and you are ready to write logic.

What the structure buys you

Think of cargo new as ordering a pre-wired workshop. You don't get just an empty room. You get the workbench, the tool rack, the power outlet, and a label on the door. In Rust terms, that workshop is a directory with a specific layout. The label is Cargo.toml, which tells the build system what the project is called and what external tools it needs. The workbench is src/main.rs, where your code lives.

This structure is standardized across every Rust project. When you clone a stranger's repository, you know exactly where to look. The consistency saves time. The build system expects this layout. The tooling expects this layout. The community expects this layout. Following the convention means every developer can contribute without a tutorial. Fighting the structure means fighting the ecosystem.

Minimal binary project

The most common starting point is a binary crate. This is a standalone executable. Run the command below to generate the project.

# Creates the directory and scaffolds the project files.
cargo new my_tool

# Moves into the new directory.
cd my_tool

# Compiles and runs the binary in one step.
cargo run

The output is Hello, world!. The command cargo run does two things at once. It compiles the source code into a binary and then executes that binary. This tightens the feedback loop. You edit, you run, you see the result. There is no separate build step to remember.

The binary runs. The loop is closed. You have a project.

Anatomy of the generated files

The command generates three key artifacts. Understanding them prevents confusion later.

First, Cargo.toml appears at the root. This is the manifest file. It uses TOML syntax and defines the package metadata.

# Defines the package metadata.
[package]
name = "my_tool"
version = "0.1.0"
# Specifies the Rust edition. 2021 is the current standard.
edition = "2021"

# Lists external crates the project depends on.
[dependencies]

The edition field controls the version of the Rust language syntax and standard library features. Rust 2021 is the default for new projects. It includes improvements like the ? operator on Result and Option in more contexts, and better async support. You rarely change this field. Leave it as 2021 unless you have a specific reason to target an older edition.

Second, a src directory appears containing main.rs. This file holds the entry point for the binary.

/// Entry point for the binary.
fn main() {
    // Prints the default greeting to stdout.
    println!("Hello, world!");
}

Third, a .gitignore file is created. It excludes the target directory. The target directory stores build artifacts, including compiled object files and the final binary. It can grow to gigabytes in size. Ignoring it keeps your version control repository clean and fast. The target directory is safe to delete at any time. Cargo will rebuild everything from scratch if needed.

Adding dependencies and real code

Real projects rarely stay at "Hello, world." You usually need external crates. Suppose you are building a CLI tool that reads a configuration file. You need a JSON parser. Instead of manually editing Cargo.toml, use cargo add.

# Adds the serde crate with the derive feature to dependencies.
cargo add serde --features derive

# Adds serde_json for parsing JSON strings.
cargo add serde_json

The cargo add command updates Cargo.toml and the Cargo.lock file automatically. It resolves the dependency graph and fetches the crate. This is the community standard for adding dependencies. Manually editing Cargo.toml works, but cargo add reduces typos and handles version resolution for you. It also updates the lock file, which is crucial for reproducibility.

The Cargo.lock file is generated automatically. It records the exact versions of every dependency in the tree. This ensures that every build, on every machine, uses the same code. It guarantees reproducibility. You should commit Cargo.lock to version control for binary projects. Library projects usually exclude it, but for a tool or application, the lock file is essential. It prevents "it works on my machine" bugs caused by dependency drift.

Update the source code to use the new dependency.

/// Entry point for the binary.
fn main() {
    // Demonstrates using an external crate.
    // This code won't compile without the serde_json dependency.
    let data = r#"{"key": "value"}"#;
    let parsed: serde_json::Value = serde_json::from_str(data).unwrap();
    println!("{:?}", parsed);
}

Run cargo run again. The compiler fetches the crates, compiles them, links them with your code, and executes the result. The output shows the parsed JSON structure.

Dependencies are just code you didn't write. Cargo fetches them. You use them.

Pitfalls and directory state

cargo new refuses to overwrite an existing directory. If you run cargo new my_tool and my_tool already exists, the command fails. This protection prevents accidental data loss. If you already have a directory with files and want to turn it into a Rust project, use cargo init.

# Initializes a Cargo project in the current directory.
cargo init

This command generates Cargo.toml, src/main.rs, and .gitignore inside the current directory. It leaves your existing files untouched. This is useful when you have written code in a text editor and want to add build support without moving files.

Crate names often contain hyphens for readability in URLs and manifests, but Rust identifiers cannot contain hyphens. Cargo handles this translation automatically. A crate named my-cli-tool in Cargo.toml is imported as my_cli_tool in your code. You never need to rename the directory or the crate manually to match the import syntax. The compiler handles the conversion.

The community uses cargo fmt to format code. It enforces a consistent style across projects. Run it before committing to avoid style debates in code reviews.

# Formats all source files according to community standards.
cargo fmt

Respect the directory state. Cargo protects you from overwriting work. Use the right command for the context.

Choosing the right initialization

Use cargo new when you are starting a fresh project and want Cargo to create the directory structure for you. Use cargo init when you already have a directory with source files and want to add Cargo support without moving anything. Use cargo new --lib when you are building a library crate that other projects will depend on, rather than a standalone binary. Use cargo new --name custom_name when you want the directory name to differ from the crate name, though keeping them identical is the standard convention.

Pick the scaffold that matches your starting point. The rest is just code.

Where to go next