Skip to main content
โšก Calmops

Core Concepts of Rust

Table of Contents

Borrow Checker, Rust compiler checks the code memory safety at compile time. There is no runtime overhead.

Ownership

  • Each value has a single owner: In Rust, every piece of data has a variable that is designated as its “owner.”
  • There can only be one owner at a time: A value cannot have multiple owners simultaneously. When ownership is transferred (e.g., by assigning a variable to another), the original owner loses access to the value. This is known as a “move.”
  • When the owner goes out of scope, the value is dropped: When the variable that owns a value goes out of scope, Rust automatically deallocates the memory associated with that value. This prevents memory leaks and ensures memory safety.
// This transfers the ownership of s
fn print_string(s: String) {
    println!("{s}");
}

// Borrowing: this does not transfer the ownership of s, just borrows the value
fn print_string_borrowed(s: &String) {
    println!("{s}");
}

fn print_string_slice(s: &str) {
    println!("{s}");
}

Borrowing

Borrowing allows you to temporarily access data without taking ownership. There are two types of borrows: immutable and mutable.

  • Immutable borrows (&T): Allow read-only access. You can have multiple immutable borrows at the same time.
  • Mutable borrows (&mut T): Allow read-write access, but only one mutable borrow is allowed at a time, and no immutable borrows can coexist.

Borrowing rules:

  1. At any given time, you can have either one mutable reference or any number of immutable references.
  2. References must always be valid (no dangling pointers).
fn main() {
    let mut s = String::from("hello");
    
    // Immutable borrow
    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2); // Works fine
    
    // Mutable borrow - would fail if r1 and r2 were still in scope
    let r3 = &mut s;
    r3.push_str(" world");
    println!("{}", r3);
}

Lifetimes

Lifetimes refer to the span of the program during which a reference to a piece of data is valid. They prevent dangling references by ensuring that references do not outlive the data they point to.

  • Lifetime annotations: Explicitly specify lifetimes using syntax like &'a T or &'a mut T.
  • Lifetime elision: In many cases, Rust can infer lifetimes automatically.
  • Static lifetime: 'static indicates data that lives for the entire program duration.
// Function with explicit lifetimes
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    // println!("The longest string is {}", result); // This would fail because string2 is out of scope
}

These concepts form the foundation of Rust’s memory safety guarantees without a garbage collector.

Comments