DEV Community

Cover image for Core Rust Concepts Every Developer Should Know
mihir mohapatra
mihir mohapatra

Posted on

Core Rust Concepts Every Developer Should Know

Rust is one of the most loved programming languages — and for good reason. It gives you systems-level control with memory safety guarantees, all enforced at compile time. No garbage collector, no runtime overhead, no segfaults.

This post walks through 6 core concepts you need to understand Rust properly.


Table of Contents

  1. Ownership & Move Semantics
  2. Borrowing & References
  3. Lifetimes
  4. Structs & Traits
  5. Error Handling with Result & Option
  6. Pattern Matching

1. Ownership & Move Semantics

Every value in Rust has a single owner. When the owner goes out of scope, the value is automatically dropped — no garbage collector needed. When you assign a value to another variable, ownership moves.

🦀 Rule: Each value has exactly one owner. When the owner is gone, the memory is freed. No dangling pointers, no double-frees.

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // s1 is MOVED into s2

    // println!("{}", s1); ← compile error: value moved!
    println!("{}", s2);  // ✓ s2 is the owner now
}

// To copy instead of move, use .clone():
let s3 = s2.clone();
println!("{} {}", s2, s3);  // both are valid
Enter fullscreen mode Exit fullscreen mode

Ownership flow:

s1 owns "hello"  →  move  →  s2 owns "hello"  →  drop  →  s1 is invalid
Enter fullscreen mode Exit fullscreen mode

2. Borrowing & References

Instead of moving, you can borrow a value using a reference (&). Immutable borrows allow many readers simultaneously. Mutable borrows (&mut) are exclusive — you can't have other borrows active at the same time.

The rules:

  • You can have many immutable references (&T) at once
  • OR you can have one mutable reference (&mut T) — but not both
fn print_len(s: &String) {        // borrows, does not own
    println!("length: {}", s.len());
}

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

fn main() {
    let mut s = String::from("hello");
    print_len(&s);       // s is still valid after this
    append(&mut s);
    println!("{}", s);   // "hello world"
}
Enter fullscreen mode Exit fullscreen mode

3. Lifetimes

Lifetimes are Rust's way of ensuring references never outlive the data they point to. In most cases the compiler infers them, but sometimes you need to annotate explicitly with 'a syntax.

💡 Lifetimes don't change how long data lives — they describe relationships between references so the compiler can verify safety at compile time.

// 'a means: output lives at least as long as both inputs
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let s1 = String::from("long string");
    let result;
    {
        let s2 = String::from("xyz");
        result = longest(s1.as_str(), s2.as_str());
        println!("{}", result);  // ✓ both alive here
    }
    // println!("{}", result); ← error: s2 dropped!
}
Enter fullscreen mode Exit fullscreen mode

4. Structs & Traits

Rust uses struct to define custom data types and trait to define shared behavior — similar to interfaces in other languages. A type can implement many traits. The compiler enforces trait contracts statically, with zero runtime overhead.

trait Describe {
    fn describe(&self) -> String;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Describe for Circle {
    fn describe(&self) -> String {
        format!("Circle with radius {}", self.radius)
    }
}

impl Describe for Rectangle {
    fn describe(&self) -> String {
        format!("{}×{} rectangle", self.width, self.height)
    }
}

fn print_shape(shape: &impl Describe) {
    println!("{}", shape.describe());
}

fn main() {
    print_shape(&Circle { radius: 3.0 });
    print_shape(&Rectangle { width: 4.0, height: 5.0 });
}
Enter fullscreen mode Exit fullscreen mode

Output:

Circle with radius 3
4×5 rectangle
Enter fullscreen mode Exit fullscreen mode

5. Error Handling with Result & Option

Rust has no exceptions. Instead, functions that can fail return Result<T, E> and functions that may return nothing return Option<T>. The ? operator makes propagating errors ergonomic.

use std::num::ParseIntError;

fn double_parse(s: &str) -> Result<i32, ParseIntError> {
    let n = s.parse::<i32>()?;  // ? returns early on error
    Ok(n * 2)
}

fn main() {
    match double_parse("21") {
        Ok(val)  => println!("Result: {}", val),  // 42
        Err(e)   => println!("Error: {}", e),
    }

    // Option: Some or None
    let items = vec![1, 2, 3];
    if let Some(first) = items.first() {
        println!("first: {}", first);
    }
}
Enter fullscreen mode Exit fullscreen mode

Comparison with other languages:

Language Error model
Rust Result<T, E> — explicit, compiler-enforced
Go (value, error) — explicit but not enforced
Java/Python Exceptions — implicit, can be ignored
C Return codes — easy to ignore

6. Pattern Matching

The match expression is Rust's powerhouse. It's exhaustive — the compiler forces you to handle every case. You can match on values, types, struct fields, ranges, and more, all with destructuring.

enum Command {
    Quit,
    Move { x: i32, y: i32 },
    Print(String),
}

fn handle(cmd: Command) {
    match cmd {
        Command::Quit               => println!("Quit!"),
        Command::Move { x, y }    => println!("Move to ({x},{y})"),
        Command::Print(msg)        => println!("Msg: {msg}"),
    }
}

// Guards and ranges
let score = 85;
let grade = match score {
    90..=100 => "A",
    80..=89  => "B",
    70..=79  => "C",
    _        => "F",
};
println!("Grade: {grade}");
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

These six concepts form the backbone of Rust's unique approach to safe, fast systems programming:

Concept What it gives you
Ownership Memory safety without a GC
Borrowing Share data safely, concurrently
Lifetimes Compile-time dangling pointer prevention
Traits Zero-cost polymorphism
Result/Option Explicit, unignorable error handling
Pattern matching Exhaustive, expressive control flow

The best way to learn Rust is to fight the borrow checker — every error it throws is teaching you something real about memory. Start with The Rust Book (it's free!) and Rustlings for hands-on exercises.


If this helped you, drop a ❤️ and share with someone learning Rust!

Top comments (0)