Getting Started#
Rust’s toolchain is self-contained and well-designed. rustup manages versions, cargo handles building, testing, and dependency management. Within minutes of installation you can compile, run, test, and lint Rust code.
Installation#
# Install rustup (manages Rust versions)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Verify
rustc --version # compiler
cargo --version # build tool + package manager
rustup --version # toolchain managerToolchain Management#
rustup update # update to latest stable
rustup default stable # use stable channel
rustup default nightly # use nightly (for bleeding-edge features)
rustup component add clippy # add linter
rustup component add rustfmt # add formatter
rustup target add wasm32-unknown-unknown # add WebAssembly targetHello World#
fn main() {
println!("Hello, World!");
}rustc main.rs # compile directly
./main # run
# But in practice, always use cargo:
cargo new hello # creates a project
cd hello
cargo run # compile + runWhat’s println!?#
The ! indicates a macro, not a function. Macros are expanded at compile time — println! accepts variable argument counts and format strings that are checked at compile time.
let name = "Alice";
let age = 30;
println!("Name: {}, Age: {}", name, age); // positional
println!("Name: {name}, Age: {age}"); // inline (Rust 1.58+)
println!("{:?}", vec![1, 2, 3]); // debug format
println!("{:#?}", complex_struct); // pretty debug
Cargo#
Cargo is Rust’s build tool and package manager — equivalent to go + npm combined.
Creating a Project#
cargo new myproject # binary (executable)
cargo new mylib --lib # libraryProject Structure#
myproject/
├── Cargo.toml # manifest (dependencies, metadata)
├── Cargo.lock # exact dependency versions (commit this for binaries)
├── src/
│ ├── main.rs # binary entry point
│ └── lib.rs # library root (if library)
├── tests/ # integration tests
│ └── integration_test.rs
├── benches/ # benchmarks
│ └── bench.rs
└── examples/ # example programs
└── demo.rsCargo.toml#
[package]
name = "myproject"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1.0"
[dev-dependencies]
assert_cmd = "2.0"
tempfile = "3.0"
[profile.release]
opt-level = 3
lto = true # link-time optimization
strip = true # strip debug symbolsEssential Cargo Commands#
cargo build # compile (debug mode)
cargo build --release # compile (optimized)
cargo run # compile + run
cargo run -- arg1 arg2 # pass args to the binary
cargo test # run all tests
cargo test test_name # run matching tests
cargo bench # run benchmarks
cargo check # type-check without building (fast)
cargo clippy # lint (better than compiler warnings)
cargo fmt # format code
cargo doc --open # generate and view documentation
cargo add serde # add a dependency (Rust 1.62+)
cargo update # update dependencies
cargo tree # show dependency treecargo check vs cargo build#
cargo check runs the compiler but stops before code generation. It is 2-5× faster than a full build. Use it during development for rapid type-checking feedback.
Variables and Mutability#
Variables in Rust are immutable by default:
let x = 5;
x = 6; // ERROR: cannot assign twice to immutable variable
let mut y = 5;
y = 6; // OK — explicitly marked mutable
This is the opposite of most languages and is intentional. Immutability by default makes code easier to reason about and enables safe concurrency.
Shadowing#
You can redeclare a variable with the same name:
let x = 5;
let x = x + 1; // new binding, shadows the old one
let x = x * 2; // 12
// Shadowing can change the type
let spaces = " ";
let spaces = spaces.len(); // now an integer
Shadowing is different from mut — it creates a new variable, which can have a different type.
Comments#
// Line comment
/* Block comment */
/// Documentation comment (generates HTML docs)
/// Supports Markdown formatting.
///
/// # Examples
///
/// ```
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ```
fn add(a: i32, b: i32) -> i32 {
a + b
}
//! Module-level documentation (placed at the top of a file)
//! Describes the purpose of the module or crate.
Documentation comments with /// are tested by cargo test — the code in # Examples blocks is compiled and run.
Printing and Debugging#
// Standard output
println!("value: {}", x); // Display trait
println!("debug: {:?}", x); // Debug trait
println!("pretty: {:#?}", x); // Debug with indentation
print!("no newline"); // without newline
eprintln!("error: {}", msg); // to stderr
// Debug during development
dbg!(x); // prints file:line, expression, and value to stderr
dbg!(&my_vec); // returns the value, so you can chain: dbg!(x + 1)
Deriving Debug#
#[derive(Debug)]
struct User {
name: String,
age: u32,
}
let u = User { name: "Alice".into(), age: 30 };
println!("{:?}", u); // User { name: "Alice", age: 30 }
println!("{:#?}", u); // pretty-printed
Cargo Clippy (Linting)#
cargo clippy # run linter
cargo clippy -- -W clippy::pedantic # stricter checksClippy catches hundreds of patterns: unnecessary clones, redundant closures, inefficient iterations, non-idiomatic code. It is more useful than rustc warnings alone.
Configuration (clippy.toml or Cargo.toml)#
# In Cargo.toml
[lints.clippy]
pedantic = "warn"
nursery = "warn"
unwrap_used = "warn"Key Takeaways#
- Install via
rustup, build viacargo. Never userustcdirectly for real projects. cargo checkis your inner-loop tool — faster thancargo build, catches all type errors.- Variables are immutable by default. Use
mutonly when you need to modify. - Shadowing lets you reuse names (and even change types) without mutability.
println!is a macro, not a function. The!is the giveaway. Format strings are checked at compile time.cargo clippyis non-negotiable. It catches more issues than the compiler’s built-in warnings.- Documentation comments (
///) with code examples are tested bycargo test— your docs stay correct.