Quick reference guide for Rust. Essential syntax, ownership, borrowing, and common patterns.
Hello World
fn main() {
println!("Hello, World!"); // Semicolons required; braces define scope
}
Variables
let x: i32 = 42; // Immutable by default
let mut y: i32 = 10; // Mutable
let name: &str = "Alice";
let flag: bool = true;
let pi: f64 = 3.14; // Floating point
// Type inference
let z = 100; // Compiler infers i32
let text = "Hello"; // &str
Control Flow
If/Else
if x > 10 {
println!("x is big");
} else {
println!("x is small");
}
if x > 10 {
println!("big");
} else if x > 5 {
println!("medium");
} else {
println!("small");
}
// If as expression
let result = if x > 10 { "big" } else { "small" };
For Loops
for i in 0..5 { // 0 to 4 (exclusive)
println!("{}", i);
}
for i in 0..=5 { // 0 to 5 (inclusive)
println!("{}", i);
}
let nums = vec![1, 2, 3];
for n in &nums { // Borrow reference
println!("{}", n);
}
for (i, n) in nums.iter().enumerate() {
println!("{}: {}", i, n);
}
While Loops
while y > 0 {
y -= 1;
println!("{}", y);
}
// Loop with break
loop {
if y <= 0 {
break;
}
y -= 1;
}
Functions
fn add(a: i32, b: i32) -> i32 {
a + b // Expression returns value (no semicolon)
}
println!("{}", add(2, 3));
// Explicit return
fn subtract(a: i32, b: i32) -> i32 {
return a - b; // Semicolon required with return
}
// Unit type (no return value)
fn greet() {
println!("Hello!");
}
Vectors / HashMaps
Vectors
let nums = vec![1, 2, 3]; // Dynamic array
for n in &nums {
println!("{}", n);
}
let mut vec = Vec::new();
vec.push(1);
vec.push(2);
vec.pop(); // Remove and return last item
vec[0] // Access by index
vec.len() // Length
HashMaps
use std::collections::HashMap;
let mut person = HashMap::new();
person.insert("name", "Alice");
person.insert("age", "30");
println!("{}", person["name"]);
// Safe access
match person.get("name") {
Some(value) => println!("{}", value),
None => println!("Not found"),
}
// Or use unwrap_or
let name = person.get("name").unwrap_or(&"Unknown");
Arrays
let arr = [1, 2, 3]; // Fixed-size array
let arr: [i32; 3] = [1, 2, 3]; // Explicit type and size
Tuples
let point = (3, 4);
let (x, y) = point; // Destructuring
let x = point.0; // Access by index
Strings
let s1 = "Hello"; // &str (string slice)
let s2 = String::from("Hello"); // String (owned)
let s3 = s2.clone(); // Clone owned string
// String operations
let mut s = String::from("Hello");
s.push_str(" World");
s.push('!');
let len = s.len();
let slice = &s[0..5]; // String slice
// Formatting
let name = "Alice";
let greeting = format!("Hello, {}!", name);
Ownership & Borrowing
// Ownership
let s1 = String::from("hello");
let s2 = s1; // s1 moved to s2, s1 no longer valid
// println!("{}", s1); // Error! s1 no longer owns the value
// Borrowing (references)
let s1 = String::from("hello");
let len = calculate_length(&s1); // Borrow, don't move
println!("{}", s1); // Still valid
fn calculate_length(s: &String) -> usize {
s.len()
}
// Mutable references
let mut s = String::from("hello");
change(&mut s);
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
Structs
struct Person {
name: String,
age: u32,
}
let person = Person {
name: String::from("Alice"),
age: 30,
};
println!("{} is {}", person.name, person.age);
// Methods
impl Person {
fn new(name: String, age: u32) -> Person {
Person { name, age }
}
fn greet(&self) {
println!("Hello, I'm {}", self.name);
}
}
let person = Person::new(String::from("Alice"), 30);
person.greet();
Enums
enum Direction {
Up,
Down,
Left,
Right,
}
let dir = Direction::Up;
match dir {
Direction::Up => println!("Going up"),
Direction::Down => println!("Going down"),
Direction::Left => println!("Going left"),
Direction::Right => println!("Going right"),
}
// Enums with data
enum Option<T> {
Some(T),
None,
}
let some_number = Some(5);
let no_number: Option<i32> = None;
Error Handling
// Result type
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}
match divide(10.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
// Using unwrap (panics on error)
let result = divide(10.0, 2.0).unwrap();
// Using expect (custom panic message)
let result = divide(10.0, 2.0).expect("Division failed");
// Using ? operator (propagates error)
fn calculate() -> Result<f64, String> {
let x = divide(10.0, 2.0)?;
Ok(x * 2.0)
}
File Operations
use std::fs;
use std::io::{self, Write};
// Reading
let content = fs::read_to_string("file.txt")
.expect("Failed to read file");
// Writing
fs::write("file.txt", "Hello, World!")
.expect("Failed to write file");
// With error handling
match fs::read_to_string("file.txt") {
Ok(content) => println!("{}", content),
Err(e) => println!("Error: {}", e),
}
Tips
- Rust variables are immutable by default; use
mutto allow changes.
read_linehandles user input; parsing is required for numbers:use std::io; let mut input = String::new(); io::stdin().read_line(&mut input).expect("Failed to read"); let number: i32 = input.trim().parse().expect("Not a number");
- Ownership and borrowing rules prevent data races — Rust's core safety guarantee.
0..5ranges are exclusive of the end value; use0..=5for inclusive.
- Everything has a type; Rust's compiler is strict but helpful with error messages.
- Use
&for borrowing (references) and&mutfor mutable borrowing.
- Expressions (no semicolon) return values; statements (with semicolon) return unit
().
- Use
matchfor pattern matching — exhaustive and powerful.
- Use
Option<T>for values that might not exist,Result<T, E>for operations that might fail.
- The
?operator is syntactic sugar for propagating errors in functions that returnResult.
- Use
unwrap()sparingly — prefer proper error handling withmatchor?.
- Structs define data;
implblocks define methods and associated functions.