Top-Fehler von Rust-Anfängern: Fortgeschrittenes Niveau

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Top-Fehler von Rust-Anfängern: Fortgeschrittenes Niveau



Rust ist eine Sprache, die sichere Speicherverwaltung und hohe Leistung verspricht. Wenn Sie jedoch von grundlegenden Beispielen zu realen Projekten übergehen, tauchen Fehler auf, die nicht einfach durch Hinzufügen von & oder mut gelöst werden können. In diesem Artikel analysieren wir vier fortgeschrittene Fallstricke, in die Entwickler häufig tappen, die bereits die Grundlagen des borrow checkers und des Besitzes beherrschen.



Der Artikel richtet sich an diejenigen, die bereits mehrere Projekte in Rust geschrieben haben und mit Rc, RefCell, Traits und unsafe in Berührung gekommen sind. Wir werden nicht erklären, was Ausleihen ist, sondern uns auf die versteckten Fallstricke konzentrieren.



1. Speicherlecks durch zyklische Referenzen in Rc/RefCell



Der tückischste Fehler ist zu glauben, dass Rc automatisch Speicherlecks verhindert. Tatsächlich verwendet Rc Referenzzählung, und wenn zwei Objekte aufeinander verweisen, wird der Zähler nie auf Null gesetzt. Der Speicher wird nicht freigegeben.



Betrachten wir ein klassisches Beispiel mit einem Graphen:



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![] }));

// Erstellen einer zyklischen Referenz a.borrow_mut().children.push(Rc::clone(&b)); b.borrow_mut().children.push(Rc::clone(&a));

// Nach dem Verlassen von main wird der Speicher nicht freigegeben! drop(a); drop(b); // Speicherleck: Rc geht davon aus, dass es noch Referenzen gibt}


Warum passiert das? Nach drop(a) wird der Referenzzähler von a verringert, aber b enthält immer noch eine Referenz auf a über children. Gleiches gilt für b. Infolgedessen bleiben beide Objekte im Speicher.



Lösung: Verwenden Sie Weak<T> für schwache Referenzen. In Graphen ist normalerweise ein Knoten der Besitzer (starke Referenz), während die anderen schwach sind.



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

#[derive(Debug)]struct Node { value: i32, parent: RefCell<Weak<Node>>, // schwache Referenz auf den Elternknoten children: RefCell<Vec<Rc<Node>>>, // starke Referenzen auf die Kinder}

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); // Jetzt wird leaf beim Löschen von branch ebenfalls freigegeben}


2. Falsche Verwendung von Pin für selbstreferenzielle Strukturen



Strukturen, die Referenzen auf sich selbst enthalten (selbstreferenziell), sind eine häufige Ursache für undefiniertes Verhalten. Rust erlaubt es nicht, sie direkt zu erstellen, aber mit Pin und unsafe ist dies möglich. Der Fehler besteht darin zu vergessen, dass Pin nur garantiert, dass das Objekt nicht im Speicher verschoben wird, aber nicht vor internen Verschiebungen schützt.



Beispiel mit einer Future:



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

#[derive(Debug)]struct SelfReferential { data: String, pointer: *const String, // roher Zeiger auf 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

Buchempfehlungen