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),
}