Top Mistakes of Beginner Rust Developers: Advanced Level

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Top Mistakes of Beginner Rust Developers: Advanced Level



Rust is a language that promises safe memory management and high performance. However, when you move from basic examples to real projects, errors start to surface that cannot be solved by simply adding & or mut. In this article, we will examine four advanced pitfalls that developers who have already mastered the basics of the borrow checker and ownership often fall into.



The article is intended for those who have already written several projects in Rust and have encountered Rc, RefCell, traits, and unsafe. We will not explain what borrowing is, but will focus on hidden pitfalls.



1. Memory Leaks via Cyclic References in Rc/RefCell



The most insidious mistake is thinking that Rc automatically prevents leaks. In reality, Rc uses reference counting, and if two objects reference each other, the counter will never reach zero. The memory will not be freed.



Consider a classic example with a graph:



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

#[derive(Debug)]struct Node { value: i32, children: Vec<Rc<RefCell<Node>>>,}

fn main() { let a = Rc::new(RefCell::new(Node { value: 1, children: vec![] })); let b = Rc::new(RefCell::new(Node { value: 2, children: vec![] }));

// Create a cyclic reference a.borrow_mut().children.push(Rc::clone(&b)); b.borrow_mut().children.push(Rc::clone(&a));

// After exiting main, the memory will not be freed! drop(a); drop(b); // Leak: Rc thinks there are still references}


Why does this happen? After drop(a), the reference count of a decreases, but b still contains a reference to a via children. The same applies to b. As a result, both objects remain in memory.



Solution: Use Weak<T> for weak references. In graphs, usually one node is the owner (strong reference), and the rest are weak.



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

#[derive(Debug)]struct Node { value: i32, parent: RefCell<Weak<Node>>, // weak reference to parent children: RefCell<Vec<Rc<Node>>>, // strong references to children}

fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), });

let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), });

*leaf.parent.borrow_mut() = Rc::downgrade(&branch); // Now when branch is deleted, leaf will also be freed}


2. Incorrect Use of Pin for Self-Referential Structures



Structures that contain references to themselves (self-referential) are a common cause of undefined behavior. Rust does not allow creating them directly, but it is possible via Pin and unsafe. The mistake is forgetting that Pin only guarantees that the object will not be moved in memory, but does not protect against internal shifts.



Example with a future:



use std::pin::Pin;use std::marker::PhantomPinned;

#[derive(Debug)]struct SelfReferential { data: String, pointer: *const String, // raw pointer to data _pin: PhantomPinned,}

impl SelfReferential { fn new(data: String) -> Self { SelfReferential { pointer: std::ptr::null(), data, _pin: PhantomPinned, } }

fn init(self: Pin<&mut Self>) { let this = unsafe { self.get_unchecked_mut() }; this.pointer = &this.data as *const String; }}

Blogs

Book Recommendations