Your first crate
You've just installed Rust. The terminal is open. You want to print "Hello, world!" to the screen. In Python, you'd create a file and run python script.py. In Rust, you don't write a script. You create a project. This is the first mental shift. Rust is a compiled language that organizes code into crates. A crate is a unit of compilation with a manifest, source files, and a build process. You start by defining the crate structure, then you write the code. This structure scales from a single-file program to a complex system with hundreds of dependencies. You build the scaffolding first.
Cargo manages the build
Rust ships with cargo, the package manager and build tool. Cargo handles dependency resolution, compilation, linking, testing, and running. You tell Cargo what you want to build, and it executes the pipeline. Think of Cargo as a project manager. You provide the specifications; Cargo brings the crew, the materials, and the machinery. You focus on the logic. Cargo handles the logistics.
When you run cargo new, Cargo creates a directory structure. It generates a Cargo.toml manifest file and a src/main.rs source file. The manifest defines the crate name, version, edition, and dependencies. The source file contains the code. This separation is deliberate. The manifest describes the project; the source implements it.
cargo new hello_world
cd hello_world
The cargo new command creates the project. The cd command moves into the directory. You now have a valid Rust project. The next step is to write the entry point.
The entry point
Every Rust binary needs a main function. The compiler looks for fn main() in the crate root. If it doesn't find one, the build fails. The main function is where execution begins. It runs when the binary starts.
Open src/main.rs and write the code. The standard greeting uses println!, a macro that prints text to standard output with a newline.
/// The entry point for the application.
fn main() {
// Print a greeting to standard output.
// The exclamation mark indicates this is a macro, not a function.
println!("Hello, world!");
}
The /// comment is a doc comment. It documents the function. The // comments explain the implementation. The fn main() signature declares the function. The body is a block enclosed in braces. The println! macro takes a format string and expands to code that writes to stdout. The semicolon terminates the statement.
Running the code
Run the program with cargo run. Cargo reads the manifest, compiles the source, links the binary, and executes it. If the code is correct, you see the output. If there are errors, Cargo prints the diagnostics and stops.
cargo run
The output is Hello, world!. Under the hood, Cargo invokes the Rust compiler. The compiler checks syntax, types, and borrow rules. It generates object files. The linker combines them into an executable. Cargo runs the executable and captures the output. This entire process happens in one command.
Cargo caches build artifacts. If you run cargo run again without changes, Cargo skips compilation and runs the binary immediately. This keeps the feedback loop fast. If you modify the code, Cargo recompiles only the affected parts. This is incremental compilation. It saves time as the project grows.
Realistic example
A static greeting is a start. Real programs often use variables. Rust variables are immutable by default. You declare them with let. You can format them into strings using placeholders in the macro.
/// Greet a user by name.
fn main() {
// Bind a string literal to an immutable variable.
let name = "Rustacean";
// Format the string using the variable.
// The {} placeholder is replaced by the value of name.
println!("Hello, {}!", name);
}
The let name = "Rustacean"; line creates a variable. The type is inferred as &str. The println! macro uses {} to insert the value. The macro checks the types at compile time. If the placeholder count doesn't match the arguments, the compiler rejects the code. This prevents runtime formatting errors.
Run this with cargo run. The output is Hello, Rustacean!. The variable is substituted into the format string. The macro handles the conversion. This is safe and efficient.
Pitfalls and errors
New Rust code often triggers compiler errors. The compiler is strict, but the messages are helpful. Learn to read them. They point to the exact location and explain the issue.
If you forget the main function, the build fails with E0601 (main function not found in crate root). The linker needs an entry point. Ensure fn main() exists in src/main.rs.
If you misspell println! as println, you get E0425 (cannot find value println in this scope). The compiler looks for a function named println. There is no such function. Macros use the ! suffix. The ! is not decoration. It signals that the item is a macro, which expands to code at compile time. Always use println!.
If you use a variable without declaring it, the compiler rejects the code with a "cannot find value" error. Rust requires explicit declarations. You can't use a name before defining it. This prevents subtle bugs from typos or scope confusion.
If you forget the semicolon after println!, you might get a type mismatch error. The main function returns (). A block without a semicolon returns the value of the last expression. println! returns (), so it works, but adding a semicolon is the convention. It signals that you are executing a statement, not returning a value. Stick to the semicolon for side-effect expressions.
Conventions and tools
The Rust community follows conventions that improve consistency. Adopt them early.
Run cargo fmt to format your code. It applies the community standard style. You don't need to argue about indentation or brace placement. The tool handles it. Run cargo fmt before committing code. It keeps the codebase uniform.
Check the edition in Cargo.toml. Cargo sets edition = "2021" by default. The edition locks the language version. Rust 2021 includes modern syntax, improved error messages, and new standard library features. Always verify the edition matches your expectations.
Use cargo check to verify code without building the binary. It runs syntax and type checks but skips code generation. It's faster than cargo build. Use it when you want quick feedback during editing.
Consider the Result return type for main. You can write fn main() -> Result<(), Box<dyn Error>>. This allows you to use the ? operator to propagate errors. If an error occurs, main returns it, and Cargo prints the error message. It turns main into a safe error handler. This is a common pattern in real Rust code.
Decision: when to use what
Use cargo run when you want to compile and execute your program in one step during development. Use cargo build when you need the binary artifact without running it, such as for testing or distribution. Use cargo check when you want to verify syntax and types quickly without the overhead of generating machine code. Use rustc directly only when you are writing a build script or need fine-grained control over compilation flags that Cargo doesn't expose.
Trust Cargo. It knows how to build your code. The ! is not decoration. It's the signal that code generation is happening. Start with cargo new. Build the structure first.