Table of Contents
- Setting Up Your Rust Environment
- Rust Fundamentals: Variables and Data Types
- Control Flow: Making Decisions and Loops
- Ownership: Rust’s Unique Safety Net
- Structs and Enums: Defining Custom Types
- Error Handling: Graceful Failure
- Hands-On Project: Building a Simple CLI Tool
- Conclusion and Next Steps
- References
1. Setting Up Your Rust Environment
Before writing your first Rust program, you’ll need to install the Rust toolchain. The easiest way is via Rustup, Rust’s official installer and version manager.
Step 1: Install Rustup
-
Linux/macOS: Open a terminal and run:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shFollow the prompts (default options work for most users).
-
Windows: Download the installer from rustup.rs and run it. Ensure you have the Visual Studio C++ Build Tools installed (required for compiling Rust code on Windows).
Step 2: Verify Installation
Close and reopen your terminal, then check the installed versions:
rustc --version # Rust compiler version (e.g., rustc 1.75.0 (82e1608df 2023-12-21))
cargo --version # Rust package manager version (e.g., cargo 1.75.0 (1d8b05cdd 2023-11-20))
Step 3: Create Your First Project
Rust uses cargo, its build tool and package manager, to manage projects. Let’s create a “Hello World” app:
cargo new hello_rust # Creates a new directory "hello_rust" with a basic project
cd hello_rust
Open the project in your favorite text editor (VS Code with the rust-analyzer extension is highly recommended). The src/main.rs file contains the entry point:
fn main() {
println!("Hello, Rust!");
}
Step 4: Run the Program
In the project directory, run:
cargo run # Compiles and runs the program
You’ll see:
Compiling hello_rust v0.1.0 (/path/to/hello_rust)
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
Running `target/debug/hello_rust`
Hello, Rust!
🎉 You’ve written and run your first Rust program!
2. Rust Fundamentals: Variables and Data Types
Rust’s syntax is clean and readable, but it has unique rules for variables and data types. Let’s dive in.
Variables: Immutable by Default
In Rust, variables are immutable (read-only) by default. To make them mutable (writable), use mut:
fn main() {
let x = 5; // Immutable: cannot be changed
// x = 6; ❌ Error: cannot assign twice to immutable variable
let mut y = 10; // Mutable: can be changed
y = 20; // ✅ Okay
println!("y is now {}", y); // Output: y is now 20
}
Data Types
Rust is statically typed: the compiler checks types at compile time. It often infers types, but you can add annotations for clarity.
Scalar Types (Single Values)
-
Integers: Signed (
i8,i16,i32,i64,i128,isize) or unsigned (u8,u16, …,usize). Default:i32.let age: u32 = 25; // Unsigned 32-bit integer let temperature: i16 = -5; // Signed 16-bit integer -
Floats:
f32(32-bit) andf64(64-bit, default).let pi: f64 = 3.14159; -
Booleans:
bool(valuestrueorfalse).let is_active = true; -
Characters:
char(Unicode scalar values, 4 bytes).let c = '🦀'; // Rust supports emojis and non-ASCII characters!
Compound Types (Groups of Values)
-
Tuples: Fixed-size collections of mixed types.
let coords: (i32, f64, bool) = (10, 3.14, true); let (x, y, z) = coords; // Destructure the tuple println!("x: {}, y: {}, z: {}", x, y, z); // Output: x: 10, y: 3.14, z: true -
Arrays: Fixed-size collections of the same type (stack-allocated).
let numbers: [i32; 3] = [1, 2, 3]; // Type: [i32; 3] (array of 3 i32s) let first = numbers[0]; // Access element (0-based index)
Exercise 1
Write a program that declares an immutable string name (e.g., “Alice”), a mutable integer score (starting at 0), increments score by 10, and prints “Hello, [name]! Your score is [score]“.
3. Control Flow: Making Decisions and Loops
Rust uses familiar control flow structures with a few Rust-specific twists.
Conditional Statements: if/else if/else
fn main() {
let temperature = 25;
if temperature > 30 {
println!("It's hot!");
} else if temperature < 10 {
println!("It's cold!");
} else {
println!("It's mild.");
}
}
Note: Unlike some languages, if conditions don’t require parentheses, and blocks must use curly braces.
Loops
Rust has three loop types:
loop: Infinite Loop (Break with break)
fn main() {
let mut count = 0;
loop {
count += 1;
if count == 5 {
break; // Exit the loop when count is 5
}
println!("Count: {}", count);
}
// Output: Count: 1, Count: 2, Count: 3, Count: 4
}
while: Loop While a Condition is True
fn main() {
let mut countdown = 3;
while countdown > 0 {
println!("{}...", countdown);
countdown -= 1;
}
println!("Go!");
// Output: 3... 2... 1... Go!
}
for: Iterate Over Collections
Use for with ranges (a..b is exclusive of b; a..=b is inclusive) or iterators:
fn main() {
// Iterate over a range (1 to 5 inclusive)
for i in 1..=5 {
println!("i: {}", i);
}
// Iterate over an array
let fruits = ["apple", "banana", "cherry"];
for fruit in fruits {
println!("Fruit: {}", fruit);
}
}
Exercise 2
Write a loop that prints all even numbers from 2 to 20 (inclusive).
4. Ownership: Rust’s Unique Safety Net
Ownership is Rust’s most distinctive feature, ensuring memory safety without a garbage collector. It has three core rules:
- Each value in Rust has a single owner.
- When the owner goes out of scope, the value is dropped (memory is freed).
- You cannot have two owners of the same value at the same time.
Example: Ownership in Action
fn main() {
let s1 = String::from("hello"); // s1 is the owner of "hello"
let s2 = s1; // s1 is MOVED to s2; s1 is now invalid
// println!("s1: {}", s1); ❌ Error: use of moved value: `s1`
println!("s2: {}", s2); // ✅ Okay: s2 is the owner
}
When s1 is assigned to s2, Rust moves ownership of the string data from s1 to s2. This prevents double-free errors (a common bug in C/C++).
Borrowing: Temporarily Accessing Values
Instead of moving ownership, you can borrow a value using references (&). Borrowed values are immutable by default:
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // Borrow s as an immutable reference
println!("The length of '{}' is {}.", s, len); // ✅ s is still valid
}
fn calculate_length(s: &String) -> usize { // Takes a reference to a String
s.len()
}
Mutable Borrowing
To modify a borrowed value, use a mutable reference (&mut). Rules:
- Only one mutable reference to a value at a time.
- No immutable references while a mutable reference exists.
fn main() {
let mut s = String::from("hello");
change(&mut s); // Mutable borrow
println!("s: {}", s); // Output: s: hello, world!
}
fn change(s: &mut String) {
s.push_str(", world!");
}
Exercise 3
Write a function greet that takes a mutable reference to a String and appends “! How are you?” to it. Call it with “Hello, Alice” and print the result.
5. Structs and Enums: Defining Custom Types
Structs and enums let you create complex data types tailored to your needs.
Structs: Grouping Related Data
A struct is a named collection of fields:
// Define a struct
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
// Instantiate a struct
let rect = Rectangle {
width: 30,
height: 50,
};
println!("Area: {}", area(&rect)); // Output: Area: 1500
}
// Function that uses a struct reference
fn area(rect: &Rectangle) -> u32 {
rect.width * rect.height
}
Methods with impl Blocks
Use impl to define methods (functions tied to a struct):
impl Rectangle {
fn area(&self) -> u32 { // &self is shorthand for &Rectangle
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 20, height: 40 };
println!("Area of rect1: {}", rect1.area()); // Output: 1500
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2)); // Output: true
}
Enums: Enumerated Values
Enums define types with a fixed set of variants:
enum IpAddr {
V4(String), // Variant with data
V6(String),
}
fn main() {
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
}
The Option Enum
Rust has no null, but Option<T> (from the standard library) represents a value that may be Some(T) or None:
fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None; // Explicit type needed for None
// Use match to handle Option
match some_number {
Some(n) => println!("Number: {}", n),
None => println!("No number"),
}
}
6. Error Handling: Graceful Failure
Rust encourages explicit error handling. Instead of exceptions, it uses the Result<T, E> enum for recoverable errors and panic! for unrecoverable ones.
The Result Enum
Result<T, E> has two variants:
Ok(T): Success, containing a value of typeT.Err(E): Failure, containing an error of typeE.
use std::fs::File;
fn main() {
let f = File::open("hello.txt"); // Returns Result<File, io::Error>
let f = match f {
Ok(file) => file,
Err(error) => panic!("Failed to open file: {:?}", error),
};
}
The ? Operator
For concise error propagation, use ? to return errors early:
use std::fs::File;
use std::io::Read;
fn read_username_from_file() -> Result<String, std::io::Error> {
let mut f = File::open("hello.txt")?; // If Err, return it immediately
let mut s = String::new();
f.read_to_string(&mut s)?; // If Err, return it
Ok(s)
}
7. Hands-On Project: Building a Simple CLI Tool
Let’s apply what we’ve learned by building a to-do list CLI app with these features:
- Add tasks.
- List tasks.
- Save tasks to a file (
tasks.txt).
Step 1: Set Up the Project
cargo new todo_cli
cd todo_cli
Step 2: Define the Task Struct
In src/main.rs, define a Task struct to represent a to-do item:
use std::fs;
use std::io;
#[derive(Debug)]
struct Task {
id: u32,
description: String,
}
Step 3: Add Task Functionality
Write functions to add tasks, list tasks, and save/load from a file:
fn add_task(description: String, tasks: &mut Vec<Task>) {
let id = tasks.len() as u32 + 1;
tasks.push(Task { id, description });
println!("Task added!");
}
fn list_tasks(tasks: &Vec<Task>) {
if tasks.is_empty() {
println!("No tasks yet!");
return;
}
println!("Tasks:");
for task in tasks {
println!("[{}] {}", task.id, task.description);
}
}
fn save_tasks(tasks: &Vec<Task>) -> Result<(), io::Error> {
let mut content = String::new();
for task in tasks {
content.push_str(&format!("{}\n", task.description));
}
fs::write("tasks.txt", content)?;
Ok(())
}
fn load_tasks() -> Result<Vec<Task>, io::Error> {
let content = fs::read_to_string("tasks.txt")?;
let mut tasks = Vec::new();
for (i, line) in content.lines().enumerate() {
tasks.push(Task {
id: (i + 1) as u32,
description: line.to_string(),
});
}
Ok(tasks)
}
Step 4: Handle Command-Line Arguments
Use std::env::args to parse CLI commands (add or list):
fn main() -> Result<(), io::Error> {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!("Usage: todo_cli [add <task>|list]");
std::process::exit(1);
}
let mut tasks = load_tasks().unwrap_or_default(); // Load tasks or start fresh
match args[1].as_str() {
"add" => {
if args.len() < 3 {
eprintln!("Usage: todo_cli add <task>");
std::process::exit(1);
}
let description = args[2..].join(" "); // Combine remaining args as task description
add_task(description, &mut tasks);
save_tasks(&tasks)?;
}
"list" => {
list_tasks(&tasks);
}
_ => {
eprintln!("Unknown command: {}", args[1]);
std::process::exit(1);
}
}
Ok(())
}
Step 5: Test the App
Run commands to add and list tasks:
cargo run add "Buy groceries"
cargo run add "Finish Rust tutorial"
cargo run list
You’ll see:
Tasks:
[1] Buy groceries
[2] Finish Rust tutorial
8. Conclusion and Next Steps
Congratulations! You’ve learned Rust’s core concepts: variables, ownership, structs, enums, error handling, and built a CLI tool. Here’s how to keep learning:
Next Steps
- Read The Rust Book: The official Rust Programming Language Book (free online).
- Practice with Rustlings: Rustlings is a collection of small exercises to build muscle memory.
- Explore Crates: Use crates.io to find libraries (e.g.,
serdefor serialization,reqwestfor HTTP requests). - Build Projects: Try a web server with
actix-web, a game withbevy, or an embedded project withembedded-hal.
9. References
- Rustup – Rust installation tool.
- The Rust Programming Language Book – Official guide.
- Rustlings – Interactive exercises.
- Rust Standard Library – API documentation.
- crates.io – Rust package registry.
Happy coding, and welcome to the Rust community! 🦀