f in x
Ownership, Borrowing, and Lifetimes in Rust: The Memory Safety Revolution
> cd .. / HUB_EDITORIALE
Analisi dei dati e metriche

Ownership, Borrowing, and Lifetimes in Rust: The Memory Safety Revolution

[2026-06-04] Author: Ing. Calogero Bono

Have you ever spent hours chasing a crash in C or C++? A null pointer, a use-after-free, a double free. We have. And we don't miss it. That's why, when we encountered Rust, it changed the way we write software. It's not just a language: it's a system of rules that eliminates entire bug classes at compile time. No garbage collector. No performance sacrifices. The secret? Three concepts that seem abstract but are brutally practical: ownership, borrowing, lifetimes.

Let's start with the problem you face daily when managing memory in a critical application: who frees what? When? If you answer “dunno”, Rust won't compile. That's why this guide is for developers who want to understand the why behind the compiler, not just copy snippets.

Ownership: One Owner Per Value

In many languages, a variable is just a name for a value. In Rust, every value has a single owner. As long as the owner exists, the value lives. When the owner goes out of scope, the value is automatically deallocated. Sounds simple, but it changes everything.

fn main() {
let s1 = String::from("hello"); // s1 owns the string
let s2 = s1; // ownership moved: s1 is no longer valid
// println!("{}", s1); // ERROR: s1 moved
println!("{}", s2); // ok
}

This is the golden rule: no data can have two owners at the same time. When you assign a variable to another, the original owner is invalidated. Why? To prevent double deallocation and dangling pointers. The compiler forces you to choose: either move it, or copy it explicitly.

Cloning to Maintain Two Owners

Sometimes you really want two independent copies. Then you use .clone(). But beware: copying costs. At Meteora Web, we've seen it in production: cloning millions of strings for free kills performance. That's why Rust forces you to do it explicitly.

let s1 = String::from("hello");
let s2 = s1.clone(); // s1 and s2 independent
println!("{}, {}", s1, s2); // ok

Borrowing: Lend Data Without Giving Up Ownership

Moving ownership constantly is cumbersome. Functions need to receive data without losing it. That's why Rust introduces borrowing – references. Instead of giving the value, you give a reference. The owner remains.

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

fn main() {
let s1 = String::from("world");
let len = calculate_length(&s1); // &s1 is a reference
println!("{} is {} characters long", s1, len); // s1 still valid
}

References are like library books: you consult them, but you don't tear them out. And the librarian (the compiler) makes sure nobody tears them while you're reading.

Two Kinds of Borrowing: Immutable and Mutable

Rust clearly distinguishes: immutable references (many, for reading) and mutable references (only one, for writing). The rule is ironclad: you cannot have a mutable reference if an immutable reference (or vice versa) already exists in the same scope. This prevents race conditions at compile time.

fn update(s: &mut String) {
s.push_str(" added");
}

fn main() {
let mut s = String::from("test");
let r1 = &s; // immutable ok
let r2 = &s; // immutable ok
// let r3 = &mut s; // ERROR: already borrowed immutably
println!("{}, {}", r1, r2);
// after r1 and r2 are no longer used
let r3 = &mut s; // now ok
update(r3);
println!("{}", s);
}

This rule seems restrictive, but it's the heart of concurrency safety. We applied it in multithreaded projects: zero mutex needed for many patterns.

Lifetimes: Ensuring References Are Valid

A reference alone isn't enough: it must point to data that still exists. Lifetimes are annotations that tie the duration of a reference to the duration of the data it references. The compiler infers them most of the time, but sometimes you must be explicit.

fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}

fn main() {
let phrase = String::from("Hello world");
let word = first_word(&phrase);
println!("{}", word);
}

In this example the compiler infers that the input and output references share the same lifetime. But with multiple input parameters, we must use 'a notation.

Annotating Lifetimes in Functions

fn longest(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

The lifetime 'a says: “the returned reference will live at least as long as both parameters.” Without this annotation, Rust wouldn't know which duration is valid.

Lifetimes in Structs

When a struct contains references, each reference must have an annotated lifetime.

struct Extract<'a> {
part: &'a str,
}

fn main() {
let text = String::from("Rust is powerful");
let first_word = Extract { part: &text[0..4] };
println!("{}", first_word.part);
}

This guarantees that the Extract never outlives the string text. If you try to return an Extract from a function after the text is destroyed, the compiler stops you.

Hands-On: What to Do Right Now

We've seen theory and code. Now let's act. Here are three concrete steps to master ownership, borrowing, and lifetimes.

1. Rewrite C Functions Using Raw Pointers as Safe Rust Functions

Take a C function that manipulates a string with char* and rewrite it using &str and &mut str. You'll immediately notice how many fewer checks you need.

2. Run cargo clippy and cargo check on an Existing Project

Clippy flags many non-idiomatic patterns related to borrowing and lifetimes. Fix every warning: it's the best exercise to internalize the rules.

3. Write a Small Library with a Struct Containing References

Try creating a SliceView<'a> that borrows a slice and provides methods. Force the compiler to ask for lifetime annotations and understand why they are necessary.

In Summary — What to Do Now

  • Don't fight the compiler: Rust's error messages are among the best. Read them, understand what they ask. Often the solution is to add a lifetime or replace a clone with a reference.
  • Measure the cost: clone is convenient but costly. In bottlenecks, replace clones with mutable borrowing or Rc/Arc when sharing is needed.
  • Use &str for functions that read strings: avoid &String when possible, because &str is more flexible and consistent with all string pointers.
  • Learn elision rules: the compiler infers many lifetimes on its own. Know when you need to be explicit (multiple input parameters, structs).
  • Don't fear 'static: a 'static reference lives for the entire program execution. Use it for constants and string literals.

At Meteora Web, we've integrated Rust into projects where memory safety is critical: embedded systems, high-performance network services. The initial cost of learning ownership is repaid with zero segfaults and zero race conditions. If you want to dive deeper, check the official documentation or contact us for a tailored consultation.

Sponsored Protocol

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Co-founder di Meteora Web. Ingegnere informatico, sviluppo ecosistemi digitali ad alte prestazioni. AI, automazione, SEO tecnica e infrastrutture web. Scrivo di tecnologia per rendere complesso… semplice.

[ Read Full Dossier ]

Hai bisogno di applicare questa strategia?

Esegui il protocollo di contatto per iniziare un progetto con noi.

> INIZIA_PROGETTO

Sponsored

> MW_JOURNAL

> READ_ALL()