DEV Community

Chidozie C. Okafor
Chidozie C. Okafor

Posted on • Originally published at doziestar.Medium on

Rust’s Ownership System: Memory Safety Without Garbage Collection

Rust is a system computer language that focuses on security and performance. Because of its distinctive ownership and borrowing structure, it ensures memory safety without the need for garbage collection. In this article, we’ll go over Rust’s ownership system in detail, replete with code examples, to show how it ensures memory safety.

Understanding Rust Ownership

In Rust, ownership is a fundamental notion used to manage memory. Every value in Rust has a single owner, and when the owner exits scope, the value is immediately deallocated. This removes the need for garbage collection while also protecting memory.

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1's ownership is moved to s2

// println!(", world!), s1; // This would cause a compile-time error, as s1 no longer owns the value
}
Enter fullscreen mode Exit fullscreen mode

The Three Rules of Ownership

The ownership system in Rust is built on three main rules:

a. In Rust, each value has a singular, distinct owner.

b. A number can be mutable or immutable, but not both at the same time.

c. The value is immediately deallocated when the owner exits the scope.

These principles aid in the prevention of common programming errors like use-after-free, double-free, and data races.

Borrowing and References

Borrowing gives you temporary access to a number while keeping ownership of it. In rust, there are two kinds of borrowing:

a. Immutable borrowing: Your code can have read-only access to the same number, preventing data races.

b. Mutable borrowing: Each value can only have one mutable reference, preventing data races and guaranteeing exclusive access.

fn main() {
    let s = String::from("hello");

    let len = calculate_length(&s); // Immutable borrow
    println!("The length of '{}' is {}.", s, len);

    let mut s = String::from("hello");
    change(&mut s); // Mutable borrow
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

fn change(some_string: &mut String) {
    some_string.push_str(", world!");
}
Enter fullscreen mode Exit fullscreen mode

Lifetimes

Lifetimes help the compiler determine how long a reference should be valid, preventing dangling references and preserving memory safety.

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}

fn longest<'a> (x: &'a str, y: &'a str) &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
Enter fullscreen mode Exit fullscreen mode

Ownership in Practice: A Real-World Example

Consider reading and processing a file’s data. Manual memory management or garbage collection would be needed in other languages to prevent memory leaks. Memory is immediately deallocated by Rust’s ownership system when the owner exits scope.

use std::fs::File;
use std::io::{BufRead, BufReader};

fn main() {
let file = File::open("input.txt"). expect("Unable to open file");
    let reader = BufReader::new(file);

    for line in reader.lines() {
        let line = line. expect("Unable to read line");
        process_line(&line);
    }
}

fn process_line(line: &str) {
    // Process the line
}
Enter fullscreen mode Exit fullscreen mode

Rust’s ownership system is a novel method of memory management that provides a unique combination of safety and performance. Rust guarantees memory safety without the need for garbage collection by enforcing strict rules on ownership, borrowing, and lifetimes. This not only helps to avoid common programming errors, but it also allows developers to confidently create high-performance and reliable systems. We can see how Rust’s ownership system effectively manages memory allocation and deallocation through the code examples given, allowing developers to concentrate on writing efficient and safe code.

Top comments (0)