blense.
HomeFrontendCodeAINewsGuidesCheatsheetsAbout

Technology decoded, always.

A curated read on AI, product, and the culture behind the code. No noise, no hype.

blense.

Technology with focus. Stories about what innovation really changes.

Sections

FrontendArtificial IntelligenceCode & DevTech News

Blense

AboutRSSPrivacy policyTermsCookies
© 2026 Blense · blense.fun
Code & Development

Memory management in Rust: ownership, borrowing, and lifetimes demystified

C gives you total control—and a loaded gun. Go uses a Garbage Collector—and latency spikes. Rust ensures memory safety at compile time, zero cost.

Gby Genildo SouzaJun 187 min read
Memory management in Rust: ownership, borrowing, and lifetimes demystified

The problem that every language solves differently

Every language needs to answer a fundamental question: when can the memory a value occupied be freed? The answer defines everything — performance, security, predictability.

The elite dev's arsenal.
Microfrontends with Angular and Nx: what nobody tells you15 minJavaScript Event Loop: The Heart of Non-Blocking Concurrency — Revisited in 20256 minTanStack Query: How to Eliminate Manual Server State Management in React6 minCopilot CLI Becomes an Agent: Writes Code, Runs Tests, and Fixes Bugs Without Prompting3 min
📝
The Fundamental Memory Management Question

Every programming language must decide when memory can be freed, impacting performance, safety, and developer responsibility.

Manual management (C/C++) offers control but risks leaks, double frees, and use-after-free errors, causing many vulnerabilities.

Garbage collection (Go) automates freeing but causes unpredictable pauses, while Rust’s ownership model enforces safe, precise memory freeing at compile time without runtime costs.

C / C++ — manual control

You allocate and you free. Maximum performance, maximum responsibility. Forgot one? free(): memory leak. Freed twice: double-free. Accessed after freeing: use-after-free. 70% of Microsoft's vulnerabilities came from here.

Go — Garbage Collector

The runtime decides. You don't manage memory — the GC scans and frees what is no longer in use. Safe, but with a cost: unpredictable periodic pauses. Discord migrated because the GC caused spikes every 2 minutes.

Rust — Ownership

The compiler decides. Rules verified at compile time ensure that memory is freed exactly when it should be — no GC, no manual management, no runtime surprises.

Ownership: every value has an owner

Rust's ownership system has three simple rules. All of the language's memory safety derives from them.

1
Each value has exactly one owner

Uma variável é a dona do valor que ela armazena. Não existe valor sem dono.

2
There can only be one owner at a time

Quando você atribui um valor a outra variável, a propriedade se transfere — a variável original deixa de ser válida.

3
When the owner goes out of scope, the value is dropped

Sem GC, sem free(). O compilador insere a liberação automaticamente quando a variável deixa de existir.

In practice, it looks like this:

JAVASCRIPT
let s1 = String::from("blueprint");let s2 = s1; // ownership se transfere pra s2println!("{}", s1); // ❌ erro de compilação: s1 não é mais válidaprintln!("{}", s2); // ✅ s2 é a nova dona

In C or Go, this code would work — and you would have two pointers pointing to the same data in memory. In Rust, the compiler refuses it immediately. There is no chance of a double-free or accidental use of a value that has already been moved.

ℹ️

Why String and not &str? Types that live on the stack (like integers) are copied automatically — they are cheap enough. Types that live on the heap (like String) transfer ownership. If you want to copy a String, you need to call .clone() explicitly — which makes the cost of the operation visible in the code.

Borrowing: using without being the owner

If ownership were the only way to access data, you would have to transfer ownership every time you passed something to a function — and receive it back later. Infeasible.

Ownership Transfer vs Borrowing in Rust Ownership transfer Borrowing (reference) Pass reference Variable A: owns String Owns the heap data Variable B: takes ownership Ownership transferred from A Reference to String Borrowed from Variable A Function receiving &String Uses reference without ownership
Ownership Transfer vs Borrowing in Rust

That is where borrowing comes in: you lend a reference to the value without transferring ownership.

TEXT
fn tamanho(s: &String) -> usize { // recebe uma referência, não o valor    s.len()}let minha_string = String::from("blueprint");let tam = tamanho(&minha_string); // passa a referênciaprintln!("{} tem {} caracteres", minha_string, tam);// ✅ minha_string ainda é válida — ownership não foi transferida

Mutable references — and the most important rule of borrowing

You can lend mutability too — but with a crucial restriction:

TEXT
let mut s = String::from("blueprint");let r1 = &mut s;let r2 = &mut s; // ❌ erro: não pode ter duas referências mutáveis ao mesmo tempo
TEXT
let mut s = String::from("blueprint");{    let r1 = &mut s;    r1.push_str(" blog");} // r1 sai de escopo aquilet r2 = &mut s; // ✅ agora pode — r1 já não existe

This rule eliminates data races at compile time. In Go or C, two threads being able to write to the same data at the same time is a ticking time bomb. In Rust, the compiler simply doesn't let this happen.

📝
Borrowing rules prevent data races at compile time

Rust enforces that you can have multiple immutable references or exactly one mutable reference to data at a time, never both simultaneously. This strict rule ensures that data races cannot occur during concurrent access.

Unlike languages like Go or C, where simultaneous writes by multiple threads can cause unpredictable bugs, Rust's compiler prevents such scenarios before the program even runs.

This compile-time guarantee is a key advantage of Rust, eliminating an entire class of concurrency errors by design.

💡

The complete borrowing rule: you can have any number of immutable references or exactly one mutable reference — never both at the same time. This is enough to eliminate an entire category of concurrency bugs.

Lifetimes: when the compiler needs help

The Rust compiler can infer most ownership and borrowing situations automatically. But there is one case where it needs an explicit hint: when a function returns a reference, and the compiler needs to know which of the parameters it comes from.

TEXT
// Sem lifetime — o compilador não sabe de onde vem a referência retornadafn maior(x: &str, y: &str) -> &str { // ❌ erro de compilação    if x.len() > y.len() { x } else { y }}
TEXT
// Com lifetime — o compilador sabe que o retorno vive tanto quanto x e yfn maior<'a>(x: &'a str, y: &'a str) -> &'a str { // ✅    if x.len() > y.len() { x } else { y }}

The 'a is a lifetime annotation. It doesn't create anything — it just tells the compiler: "the returned reference lives for the same amount of time as both parameters." With this information, it can verify that you will never return a reference to something that has already been freed.

⚠️

Lifetimes are the biggest friction point in Rust. The good news: the compiler needed explicit annotations in much simpler cases in the past. Modern versions of Rust infer lifetimes in most cases with the lifetime elision rules — you only need to annotate when the situation is genuinely ambiguous.

Comparing in practice: the same bug in three languages

Use-after-free is one of the most common and dangerous vulnerabilities. See how each language handles it:

C: Undefined Behavior

Memory is manually allocated and freed.

Use-after-free occurs when accessing freed memory.

Compiler does not catch the error.

Program compiles and runs but behavior is unpredictable.

Leads to potential security vulnerabilities.

Go: Runtime GC Prevention

Garbage collector manages memory automatically.

Prevents premature freeing of memory.

Use-after-free is avoided at runtime.

No compile-time errors related to memory use.

Adds runtime overhead due to GC management.

TEXT
/* C — compila, executa, comportamento indefinido */char *s = malloc(10);free(s);printf("%s", s); // acessa memória liberada — undefined behavior
TEXT
// Go — GC previne liberação prematura, mas com custo em runtime// O GC garante que s não será liberada enquanto ainda houver referênciass := "blueprint"fmt.Println(s) // sempre seguro, mas o GC adiciona overhead
TEXT
// Rust — erro em tempo de compilação, zero custo em runtimelet s = String::from("blueprint");drop(s); // libera explicitamenteprintln!("{}", s); // ❌ erro de compilação — o compilador recusa// esse código nunca chega em produção

This is the fundamental difference: C discovers the problem in production (when it has already caused damage). Go prevents it at runtime with a GC (but pays the latency cost). Rust prevents it at compile time — at zero runtime cost.

What the compiler guarantees for you
  • 1Ownership — each value has an owner. When the owner goes out of scope, the memory is freed automatically.
  • 2Move semantics — transferring a value invalidates the original variable. Double-free is impossible.
  • 3Borrowing — referencing without transferring. Immutable: as many as you want. Mutable: only one at a time.
  • 4No data race — two simultaneous mutable references will not compile. Safe concurrency by design.
  • 5Lifetimes — the compiler verifies that no reference outlives the value it points to. Dangling pointers are impossible.
  • 6Zero runtime cost — all of this is verified at compile time. In production, the binary is as fast as C.

Keep exploring
Want more content like this?

Check out other articles in the same vein and keep the momentum.

See more articles
#Rust#Ownership#Go#Lifetimes

The elite dev's arsenal.

Microfrontends with Angular and Nx: what nobody tells you
Code & Development

Microfrontends with Angular and Nx: what nobody tells you

Most tutorials show how to build microfrontends in minutes, but few reveal the architectural debt that emerges after months of production deployments.

Genildo Souza · Jul 28 · 15 min
JavaScript Event Loop: The Heart of Non-Blocking Concurrency — Revisited in 2025
Code & Development

JavaScript Event Loop: The Heart of Non-Blocking Concurrency — Revisited in 2025

The Event Loop is not just a simple loop, but a sophisticated coordination mechanism between Call Stack, queues, and runtime. Essential for JS concurrency, it enables thousands of asynchronous operations without blocking the UI. This guide explores its structure, differences between Node.js and browsers, common bugs, advanced techniques, and 2025 trends like Structured Concurrency and scheduler.yield().

Genildo Souza · Jul 26 · 6 min
Next.js 16 PPR: Eliminate the static vs. dynamic tradeoff and achieve Edge TTFB with fresh data
Code & Development

Next.js 16 PPR: Eliminate the static vs. dynamic tradeoff and achieve Edge TTFB with fresh data

Partial Prerendering in Next.js 16 resolves the classic rendering tradeoff, delivering static speed with dynamic data in one seamless response.

Genildo Souza · Jul 13 · 14 min
In this article
  • The problem that every language solves differently
  • Ownership: every value has an owner
  • Borrowing: using without being the owner
  • Mutable references — and the most important rule of borrowing
  • Lifetimes: when the compiler needs help
  • Comparing in practice: the same bug in three languages