VOOZH about

URL: https://blog.logrocket.com/handling-memory-leaks-rust/

⇱ Handling memory leaks in Rust - LogRocket Blog


2024-11-20
1245
#rust
Ukeje Goodness
198166
114
👁 Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

Rust’s low-level nature gives you access to resources and memory, so many developers choose Rust for their projects across various tech sectors. Ownership and borrowing are among the first concepts you’ll learn when dealing with Rust, as they form the primitives for how Rust handles memory.

👁 Rust logo over black marble background.

Once in a while, you may experience memory leaks in your Rust projects due to many factors, from unsafe code to shared references, etc. Ideally, you’ll want to fix these memory leaks and ensure your programs are efficient, which may lead to performance gains and resource safety.

🚀 Sign up for The Replay newsletter

The Replay is a weekly newsletter for dev and engineering leaders.

Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.

Memory leaks and unsafe behavior

Rust’s built-in ownership model and compile-time checks reduce the possibility and risks you’ll encounter with memory leaks, but they’re still quite possible.

Memory leaks don’t violate the ownership rules, so the borrow checker lets them slide at compile time. Leaking memory is inefficient and generally not a great idea, especially if you have resource constraints.

On the other hand, unsafe behavior can also slide if you embed it in an unsafe block. In this case, memory safety is your responsibility regardless of the operation, e.g. pointer dereferencing, manual memory allocation, or concurrency issues.

Memory Leaks via ownership and borrowing

Rust does not use a garbage collector. Instead, it uses ownership and borrowing (the borrow checker enforces the ownership model), which form the core principles for memory handling in Rust programs.

The borrow checker prevents dangling references, use-after-free errors, and data races at compile time before the compiler executes the program. Still, memory leaks can occur when memory is allocated without dropping it throughout the execution time.

Here’s an example of how I implement a doubly linked list. The program would run successfully, but there would also be a memory leak issue:

use std::rc::Rc;
use std::cell::RefCell;

struct Node {
 value: i32,
 next: Option<Rc<RefCell<Node>>>,
 prev: Option<Rc<RefCell<Node>>>,
}

fn main() {
 let first = Rc::new(RefCell::new(Node {
 value: 1,
 next: None,
 prev: None,
 }));

 let second = Rc::new(RefCell::new(Node {
 value: 2,
 next: Some(Rc::clone(&first)),
 prev: Some(Rc::clone(&first)),
 }));

 first.borrow_mut().next = Some(Rc::clone(&second));
 first.borrow_mut().prev = Some(Rc::clone(&second));

 println!("Reference count of first: {}", Rc::strong_count(&first)); 
 println!("Reference count of second: {}", Rc::strong_count(&second)); 

}

The problem with this program occurs with the circular reference between two nodes, resulting in a memory leak. Since RC smart pointers don’t handle cyclic references by default, each node holds a strong reference to the other, creating a cycle.

After the main function is executed, the reference count for the second and first variables will equal the first value, although it’s no longer accessible. This results in a memory leak since none of the nodes are deallocated:

👁 A terminal output showing a memory leaks warning in Rust with reference counts of 3 for variables, highlighting a memory leak scenario.

You can fix cases like this by:

  • Using weak references likeWeak<T> for one link direction
  • Manually breaking the cycle before the end of the function

Here’s an example where I address the reference problem with Weak pointers on the prev field:

use std::rc::{Rc, Weak};
use std::cell::RefCell;

struct Node {
 value: i32,
 next: Option<Rc<RefCell<Node>>>,
 prev: Option<Weak<RefCell<Node>>>,
}

fn main() {
 let first = Rc::new(RefCell::new(Node {
 value: 1,
 next: None,
 prev: None,
 }));

 let second = Rc::new(RefCell::new(Node {
 value: 2,
 next: Some(Rc::clone(&first)),
 prev: Some(Rc::downgrade(&first)),
 }));

 first.borrow_mut().next = Some(Rc::clone(&second));
 first.borrow_mut().prev = Some(Rc::downgrade(&second));

 println!("Reference count of first: {}", Rc::strong_count(&first)); 
 println!("Reference count of second: {}", Rc::strong_count(&second)); 

 println!("First value: {}", first.borrow().value);
 println!("Second value: {}", second.borrow().value);

 let next_of_first = first.borrow().next.as_ref().map(|r| r.borrow().value);
 println!("Next of first: {}", next_of_first.unwrap());

 let prev_of_second = second.borrow().prev.as_ref().unwrap().upgrade().unwrap();
 println!("Prev of second: {}", prev_of_second.borrow().value);
}

You can use <Weak<RefCell<Node>>> to prevent the memory leak since the Weak reference doesn’t increase the strong reference count, and the nodes can be deallocated.

The std::mem::forget function

You can intentionally use the std::mem::forget function to leak memory in your Rust project when necessary. Rust includes the function for this behavior, so the compiler considers it safe.

Even if the memory isn’t reclaimed, there’ll be no unsafe access or memory issues.

The std::mem::forget takes ownership of a value and forgets it without running the destructor, and since resources held in memory aren’t released, there will be a memory leak:

use std::mem;

fn main() {
 let data = Box::new(42);
 mem::forget(data);
}

At runtime, Rust skips the usual cleanup process, the data variable’s value is not dropped, and the memory allocated for data is leaked after the function is executed.


Over 200k developers use LogRocket to create better digital experiences

👁 Image
Learn more →

Leaking memory with unsafe blocks

Using raw pointers gives you the responsibility to manage memory. Here’s how using raw pointers in an unsafe block may lead to a memory leak:

fn main() {
 let x = Box::new(42);
 let raw = Box::into_raw(x); 

 unsafe {
 println!("Memory is now leaked: {}", *raw);
 }
}

In this case, the memory isn’t freed explicitly, and there will be a memory leak at runtime. After the program’s execution, the memory will be deallocated, so this isn’t the most critical case, but it’s not memory efficient.

Deliberately leaking memory with Box::leak

The Box::leak function allows you to leak memory deliberately. This function is proper when you need to use a value throughout runtime:

fn main() {
 let x = Box::new(String::from("Hello, world!"));
 let leaked_str: &'static str = Box::leak(x);
 println!("Leaked string: {}", leaked_str);
}

Don’t abuse this; leak is helpful if you need a static reference to meet specific API requirements.

Fixing memory leaks in Rust

The golden rule for fixing memory leaks is avoiding them in the first place, except if your use case requires you to. Following the ownership rules is always a great idea. In fact, with the borrow checker, Rust enforces great memory management practices:

  1. Use references when you need to borrow values without transferring the ownership.
  2. You can try out Miri to detect undefined behavior and catch memory-leak-related bugs.
  3. Implement the Drop trait on custom types for cleanups.
  4. Don’t use std::mem::forget unnecessarily. Check out Box<T> for automatic cleanups on heap allocations when a value is off the scope.
  5. Don’t throw unsafe blocks everywhere without reason.
  6. Use Rc<T> or Arc<T> for shared ownership of variables.
  7. Use RefCell<T> or Mutex<T> for interior mutability. They’re helpful if you need to ensure safe concurrent access.

Following these tips and building more Rust programs with lower memory requirements should provide everything you need to handle memory leaks in your Rust programs.

Conclusion

You’ve learned how memory leaks can happen in your Rust programs and how you can simulate them in necessary cases for different purposes, like having a persistent variable in a memory location at runtime.

Understanding the fundamentals of ownership, borrowing, and unsafe Rust can help manage memory and reduce memory leaks.

LogRocket: Full visibility into web frontends for Rust apps

Debugging Rust applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking the performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, try LogRocket.

LogRocket lets you replay user sessions, eliminating guesswork around why bugs happen by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings — compatible with all frameworks.

LogRocket's Galileo AI watches sessions for you, instantly identifying and explaining user struggles with automated monitoring of your entire product experience.

👁 LogRocket Dashboard Free Trial Banner

Modernize how you debug your Rust apps — start monitoring for free.

👁 Image
👁 Image
👁 Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

How to build a virtual engineering team with Gemini CLI subagents

Learn how to use Gemini CLI subagents to delegate frontend, backend, testing, and docs tasks to specialized agents with guardrails and clear ownership.

👁 Image
Emmanuel John
Jun 18, 2026 ⋅ 10 min read

Debug Next.js apps with AI agents and next-browser

Learn how next-browser gives AI agents runtime context for debugging Next.js apps, including React props, hydration, PPR, forms, and performance.

👁 Image
Emmanuel John
Jun 17, 2026 ⋅ 9 min read

Stop hardcoding LLM SDKs: Dynamic LLM routing with OpenRouter and Next.js

Build dynamic LLM routing in Next.js with OpenRouter, TanStack AI, task classification, model fallbacks, and cost-aware routing.

👁 Image
Chizaram Ken
Jun 16, 2026 ⋅ 13 min read

What is TSRX?: What JSX would look like if it were designed today

TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension — no new framework required.

👁 Image
Ikeh Akinyemi
Jun 12, 2026 ⋅ 6 min read
View all posts

Hey there, want to help make our blog better?

Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Sign up now