Level 1 · Beginner

Rust Beginner

The fundamentals — variables, ownership, control flow, functions, structs, enums, and your first taste of error handling.

Variables Types Ownership Borrowing Structs Enums Match Option/Result

Hello, Cargo

01

Rust is installed via rustup. cargo is the build tool and package manager — you'll use it constantly.

Terminal
$ cargo new hello
$ cd hello
$ cargo run
   Compiling hello v0.1.0
    Finished `dev` profile in 0.42s
     Running `target/debug/hello`
Hello, world!
src/main.rs
fn main() {
    println!("Hello, world!");
}

Variables & Types

02

Variables are immutable by default. Use mut to allow mutation. Types are usually inferred but can be annotated.

let name = "Ada";          // &str, immutable
let age: u32 = 36;         // explicit type
let mut score = 0;         // mutable
score += 10;

const MAX_USERS: u32 = 100;  // compile-time constant, must annotate
TypeExampleNotes
i32, i64, u32, u6442_i32, 1_000_u64Signed and unsigned integers
f32, f643.14_f64f64 is the default
booltrue, false1 byte
char'A', '😀'4-byte Unicode scalar
&str"hello"String slice, immutable
StringString::from("hi")Heap-allocated, growable
(T, U)(1, "two")Tuple — fixed size, mixed types
[T; N][1, 2, 3]Fixed-size array on stack
Vec<T>vec![1, 2, 3]Growable list on heap

Control Flow

03

if is an expression — it returns a value. loop is an infinite loop, while is conditional, and for iterates.

// if as expression
let label = if score >= 90 { "A" } else if score >= 80 { "B" } else { "C" };

// for over a range
for i in 0..5 {
    println!("{i}");        // 0 1 2 3 4
}

// for over a collection
let names = vec!["Ada", "Linus", "Grace"];
for name in &names {
    println!("{name}");
}

// loop with a return value
let answer = loop {
    let x = compute();
    if x > 100 { break x * 2; }
};

Functions

04

Functions use fn, require type annotations for parameters, and the final expression (without semicolon) is the return value.

fn add(a: i32, b: i32) -> i32 {
    a + b               // no semicolon = return value
}

fn greet(name: &str) {  // no -> means returns unit ()
    println!("Hello, {name}!");
}

fn main() {
    let sum = add(2, 3);
    greet("Ada");
}

Ownership

05

Rust's killer feature. Three rules:

  1. Each value has exactly one owner.
  2. When the owner goes out of scope, the value is dropped.
  3. You can have many immutable references or one mutable reference — never both at the same time.
Move semantics
let s1 = String::from("hi");
let s2 = s1;                  // ownership moves to s2
// println!("{s1}");           // ❌ compile error — s1 no longer valid

let n1 = 5;
let n2 = n1;                  // ✅ Copy types are duplicated (i32 implements Copy)
println!("{n1} {n2}");        // both valid

Borrowing with & and &mut

References let you read or modify a value without taking ownership.

fn length(s: &String) -> usize {  // immutable borrow
    s.len()
}

fn shout(s: &mut String) {        // mutable borrow
    s.push_str("!!!");
}

fn main() {
    let mut name = String::from("hello");
    let len = length(&name);       // read-only borrow
    shout(&mut name);              // mutable borrow
    println!("{name} ({len})");    // "hello!!! (5)"
}

Structs

06

Structs group related data. Add methods with impl.

struct User {
    name: String,
    email: String,
    active: bool,
}

impl User {
    // Associated function (no &self) — acts like a constructor
    fn new(name: &str, email: &str) -> Self {
        Self {
            name: name.to_string(),
            email: email.to_string(),
            active: true,
        }
    }

    // Method (takes &self)
    fn deactivate(&mut self) {
        self.active = false;
    }
}

let mut u = User::new("Ada", "ada@example.com");
u.deactivate();

Enums & Match

07

Enums can hold data. match handles every variant — the compiler enforces it.

enum Shape {
    Circle(f64),                // radius
    Square(f64),                // side
    Rectangle { w: f64, h: f64 },
}

fn area(s: &Shape) -> f64 {
    match s {
        Shape::Circle(r) => std::f64::consts::PI * r * r,
        Shape::Square(s) => s * s,
        Shape::Rectangle { w, h } => w * h,
    }
}

let s = Shape::Rectangle { w: 3.0, h: 4.0 };
println!("{}", area(&s));   // 12

Option & Result

08

Rust has no null and no exceptions. Option<T> models "maybe a value", Result<T, E> models "success or error".

fn find(name: &str) -> Option<u32> {
    if name == "Ada" { Some(36) } else { None }
}

match find("Ada") {
    Some(age) => println!("Age: {age}"),
    None => println!("Not found"),
}

// Shortcut: unwrap_or, ?, if let
let age = find("Ada").unwrap_or(0);
if let Some(age) = find("Ada") { println!("{age}"); }
Result & the ? operator
use std::num::ParseIntError;

fn parse_age(s: &str) -> Result<u32, ParseIntError> {
    let n = s.parse::<u32>()?;   // ? returns the error early
    Ok(n)
}

fn main() {
    match parse_age("42") {
        Ok(n)  => println!("Got {n}"),
        Err(e) => println!("Error: {e}"),
    }
}

Next up

Comfortable with ownership and enums? Continue with Rust Mid-Level — traits, generics, lifetimes, iterators, and smart pointers.

Articles Tags Products