Rust Basics
Learn Rust programming fundamentals
Rust Basics
Rust is a systems programming language focused on safety, speed, and concurrency. This guide covers the fundamentals you need to get started with Rust programming.
Variables
Rust variables are immutable by default:
// Immutable variable (default)
let x = 5;
// x = 6; // Error! Cannot assign to immutable variable
// Mutable variable
let mut y = 5;
y = 6; // OK
// Type annotation
let age: i32 = 30;
let name: &str = "Rust";
// Shadowing (redeclaring with same name)
let x = 5;
let x = x + 1; // x is now 6
let x = "six"; // x is now a string
Data Types
Rust has strong static typing:
// Integers
let integer: i32 = 42; // 32-bit signed
let unsigned: u32 = 42; // 32-bit unsigned
let big: i64 = 1000000; // 64-bit signed
let small: i8 = 127; // 8-bit signed
// Floating point
let float: f64 = 3.14; // 64-bit float
let float32: f32 = 3.14; // 32-bit float
// Boolean
let is_active: bool = true;
// Character (4 bytes, Unicode)
let character: char = 'R';
// String slices
let string_slice: &str = "Hello, Rust!";
let owned_string: String = String::from("Hello, Rust!");
Vectors
Vectors are growable arrays:
// Creating vectors
let mut fruits = vec!["apple", "banana", "orange"];
let numbers: Vec = vec![1, 2, 3, 4, 5];
let empty: Vec = Vec::new();
// Accessing elements
fruits[0] // "apple" (panics if out of bounds)
fruits.get(0) // Some("apple") (returns Option)
fruits.first() // Some(&"apple")
fruits.last() // Some(&"orange")
// Adding elements
fruits.push("grape"); // Add to end
fruits.insert(0, "kiwi"); // Insert at index
// Removing elements
fruits.pop(); // Remove last (returns Option)
fruits.remove(0); // Remove at index
// Vector methods
fruits.len() // Get length
fruits.is_empty() // Check if empty
fruits.contains(&"apple") // Check if contains
Ownership
Rust's unique feature - memory safety without garbage collection:
// Ownership transfer
let s1 = String::from("hello");
let s2 = s1; // s1 is moved to s2, s1 is no longer valid
// println!("{}", s1); // Error! s1 no longer valid
// Borrowing (references)
let s1 = String::from("hello");
let len = calculate_length(&s1); // Borrow s1
println!("{}", s1); // OK, s1 still valid
fn calculate_length(s: &String) -> usize {
s.len()
}
// Mutable references
let mut s = String::from("hello");
change(&mut s);
println!("{}", s); // "hello, world"
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
Structs
Structs group related data:
// Defining a struct
struct Person {
name: String,
age: u32,
}
// Creating instances
let person = Person {
name: String::from("John"),
age: 30,
};
// Accessing fields
println!("{} is {} years old", person.name, person.age);
// Mutable struct
let mut person = Person {
name: String::from("Jane"),
age: 25,
};
person.age = 26;
// Struct methods
impl Person {
fn new(name: String, age: u32) -> Person {
Person { name, age }
}
fn introduce(&self) -> String {
format!("Hi, I'm {} and I'm {} years old", self.name, self.age)
}
}
let person = Person::new(String::from("Alice"), 28);
println!("{}", person.introduce());
Loops
Rust offers several iteration methods:
// Loop (infinite loop)
let mut count = 0;
loop {
count += 1;
if count == 5 {
break;
}
}
// While loop
let mut count = 0;
while count < 5 {
println!("{}", count);
count += 1;
}
// For loop (most common)
let fruits = vec!["apple", "banana", "orange"];
for fruit in &fruits {
println!("{}", fruit);
}
// For loop with index
for (index, fruit) in fruits.iter().enumerate() {
println!("{}: {}", index, fruit);
}
// Range iteration
for i in 0..5 {
println!("{}", i); // 0, 1, 2, 3, 4
}
for i in 0..=5 {
println!("{}", i); // 0, 1, 2, 3, 4, 5
}
Conditionals
Control flow with if/else statements:
// If/else
let age = 20;
if age >= 18 {
println!("Adult");
} else if age >= 13 {
println!("Teenager");
} else {
println!("Child");
}
// If as expression
let status = if age >= 18 {
"Adult"
} else {
"Minor"
};
// Match expression (like switch)
let grade = 'B';
match grade {
'A' => println!("Excellent"),
'B' => println!("Good"),
'C' => println!("Average"),
_ => println!("Needs improvement"),
}
// Match with values
let number = 5;
match number {
1 => println!("One"),
2 | 3 | 5 | 7 => println!("Prime"),
4 | 6 | 8 | 9 => println!("Composite"),
_ => println!("Other"),
}
Functions
Functions are defined with fn:
// Simple function
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
// Function with multiple parameters
fn add(x: i32, y: i32) -> i32 {
x + y // No semicolon = return value
}
// Function returning multiple values (tuple)
fn get_name_and_age() -> (String, u32) {
(String::from("John"), 30)
}
let (name, age) = get_name_and_age();
// Closures (anonymous functions)
let add_one = |x| x + 1;
let result = add_one(5); // 6
// Closure with type annotation
let add = |x: i32, y: i32| -> i32 { x + y };
// Higher-order functions
let numbers = vec![1, 2, 3, 4];
let doubled: Vec = numbers.iter().map(|x| x * 2).collect();
let evens: Vec<&i32> = numbers.iter().filter(|x| *x % 2 == 0).collect();
Enums
Enums define a type with multiple variants:
// Simple enum
enum Direction {
North,
South,
East,
West,
}
// Enum with data
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
// Using enums
let direction = Direction::North;
let msg = Message::Write(String::from("hello"));
// Match with enums
match direction {
Direction::North => println!("Going north"),
Direction::South => println!("Going south"),
Direction::East => println!("Going east"),
Direction::West => println!("Going west"),
}
Option and Result
Rust uses Option and Result for error handling:
// Option - Some(value) or None
let some_number = Some(5);
let no_number: Option = None;
match some_number {
Some(value) => println!("Got: {}", value),
None => println!("No value"),
}
// Result - Ok(value) or Err(error)
fn divide(a: f64, b: f64) -> Result {
if b == 0.0 {
Err(String::from("Cannot divide by zero"))
} else {
Ok(a / b)
}
}
match divide(10.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(error) => println!("Error: {}", error),
}