Your First Rust Program
Every programming journey begins with a simple program. In Rust that means writing a main function, using the println! macro, and getting comfortable with how the compiler and Cargo work together to turn your source code into a running binary.
Hello, World!
Create a new project with Cargo, then open src/main.rs. It already contains the classic Hello World program:
cargo new hello_world cd hello_world
fn main() {
println!("Hello, World!");
}Run it with a single command:
cargo run
Compiling hello_world v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.42s
Running `target/debug/hello_world`
Hello, World!The main function
Every Rust binary must have a main function — it is the program's entry point. When you run the compiled binary, execution begins at the first line of main and ends when main returns.
fn main() {
// All code in a binary starts here.
// The program exits when this function returns.
}fnis the keyword that declares a functionmainis the special name the runtime looks forThe empty parentheses
()mean this function takes no parametersThe curly braces
{}delimit the function body
println! — a macro, not a function
Notice the exclamation mark after println. That ! tells you this is a macro, not an ordinary function call. Macros are expanded at compile time and can do things ordinary functions cannot — like accept a variable number of arguments with different types.
Function | Macro | |
|---|---|---|
Syntax | name(args) | name!(args) |
Argument count | Fixed by signature | Variable — println! accepts any number |
Evaluated | At runtime | Expanded at compile time |
Example | Vec::new() | println!("hi"), vec![], format!() |
Format strings
println! uses format strings with curly-brace placeholders {} to embed values in output. Each {} is replaced by the next argument in order.
fn main() {
let name = "Alice";
let age = 30;
// Basic placeholder
println!("Hello, {}!", name);
// Multiple placeholders
println!("{} is {} years old.", name, age);
// Named placeholders (Rust 1.58+)
println!("{name} is {age} years old.");
// Debug output with {:?}
let numbers = vec![1, 2, 3];
println!("Numbers: {:?}", numbers);
// Pretty-printed debug with {:#?}
println!("Numbers:
{:#?}", numbers);
// Padding and alignment
println!("{:>10}", "right"); // right-aligned in 10 chars
println!("{:<10}", "left"); // left-aligned
println!("{:^10}", "center"); // centered
println!("{:0>5}", 42); // zero-padded: 00042
}Hello, Alice!
Alice is 30 years old.
Alice is 30 years old.
Numbers: [1, 2, 3]
Numbers:
[
1,
2,
3,
]
right
left
center
00042Compiling manually with rustc
Cargo is the standard tool, but you can also compile a single file directly with rustc, the Rust compiler:
# Compile a single file directly rustc src/main.rs # Produces an executable called 'main' (or 'main.exe' on Windows) ./main # cargo run does all of this automatically and is always preferred cargo run
Declaring variables and printing them
Variables in Rust are declared with the let keyword and are immutable by default. The compiler infers the type from the assigned value.
fn main() {
let greeting = "Hello";
let target = "Rust";
let version = 2021;
println!("{}, {}! Edition {}", greeting, target, version);
// Rust infers the type from the value
let x = 5; // i32
let y = 3.14; // f64
let is_ready = true; // bool
println!("x={}, y={:.2}, ready={}", x, y, is_ready);
}Hello, Rust! Edition 2021 x=5, y=3.14, ready=true
Reading user input
To read a line typed by the user you use Rust's standard library std::io. Bring it into scope with a use statement, then call stdin().read_line.
use std::io;
fn main() {
println!("What is your name?");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
// trim() removes the trailing newline character
let name = input.trim();
println!("Hello, {}! Welcome to Rust.", name);
}What is your name? Alice Hello, Alice! Welcome to Rust.
use std::iobrings the io module into scopeString::new()creates an empty, growable string on the heapmutmakes the variable mutable — without it you cannot callread_lineread_line(&mut input)appends the typed line intoinput, including the newline.expect(...)handles the error — the program panics with that message if reading fails.trim()strips the trailing\n(or\r\non Windows) thatread_lineleaves in
A mini guessing game
Here is a small but complete program that combines variables, user input, type conversion, and a comparison — a taste of what Rust programs look like as they grow.
use std::io;
fn main() {
let secret = 42;
println!("Guess the number (between 1 and 100):");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
// Parse the string as a 32-bit signed integer
let guess: i32 = input.trim().parse().expect("Please enter a number");
if guess == secret {
println!("You got it! The number was {}.", secret);
} else if guess < secret {
println!("Too low! The answer was {}.", secret);
} else {
println!("Too high! The answer was {}.", secret);
}
}Guess the number (between 1 and 100): 35 Too low! The answer was 42.
Anatomy of a Rust source file
Rust source files share a consistent top-level structure regardless of size:
// 1. Crate-level attributes (optional, go at the very top)
#![allow(unused)]
// 2. Use statements — bring external items into scope
use std::io;
use std::collections::HashMap;
// 3. Constants and type aliases (optional)
const MAX_SIZE: usize = 100;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
// 4. Helper function definitions
fn double(x: i32) -> i32 {
x * 2
}
// 5. The main function (required for binaries)
fn main() {
let result = double(21);
println!("Result: {}", result);
}