Einleitung
Code Review ist ein wesentlicher Bestandteil der professionellen Softwareentwicklung. Im Rust-Ökosystem, wo Speichersicherheit und Leistung an erster Stelle stehen, bekommt das Code-Review eine besondere Bedeutung. Die falsche Verwendung von unsafe, das Ignorieren der Ownership-Regeln oder implizite Panics können alle Vorteile der Sprache zunichtemachen.
In diesem Artikel besprechen wir, worauf man beim Code-Review in Rust achten sollte, wie man Feedback strukturiert und welche Tools bei der Automatisierung von Prüfungen helfen. Das Material ist sowohl für Anfänger als auch für erfahrene Entwickler nützlich, die den Review-Prozess optimieren möchten.
1. Sicherheit: Prüfung von unsafe-Code und impliziten Panics
Rust garantiert Speichersicherheit, aber unsafe bedeutet den Verzicht auf diese Garantien. Jeder unsafe-Block muss sorgfältig geprüft werden.
Was ist in unsafe-Code zu prüfen?
- Korrektheit von Zeigern: Gibt es Dereferenzierungen von Null- oder Hängenden Zeigern?
- Datenausrichtung: Ist die Ausrichtung bei der Konvertierung von Rohzeigern korrekt?
- Aliasing: Gibt es Verstöße gegen Aliasing-Regeln (z. B. gleichzeitige mutable und immutable Ausleihen)?
- Invarianten: Werden die Invarianten von Datenstrukturen eingehalten (z. B. bleibt ein binärer Heap ein Heap)?
// Schlechtes Beispiel: unsichere Konvertierung ohne Prüfung der Ausrichtungfn bad_unsafe(data: &[u8]) -> &[u32] { unsafe { std::slice::from_raw_parts( data.as_ptr() as *const u32, data.len() / 4 ) }}
// Gutes Beispiel: mit Prüfung der Ausrichtungfn good_unsafe(data: &[u8]) -> Option<&[u32]> { if data.len() % 4 != 0 || data.as_ptr() as usize % 4 != 0 { return None; } Some(unsafe { std::slice::from_raw_parts( data.as_ptr() as *const u32, data.len() / 4 ) })}Implizite Panics
In Rust gibt es viele Operationen, die eine Panic auslösen können: unwrap(), expect(), panic!(), Division durch Null, Zugriff außerhalb der Array-Grenzen. Beim Code-Review sollte man darauf achten:
- Verwenden Sie
unwrap()nur in Tests oder wenn ein Fehler wirklich unmöglich ist. - Bevorzugen Sie
unwrap_or(),unwrap_or_else()oder die Verarbeitung mitmatch. - Prüfen Sie, ob alle
as-Konvertierungen keine Daten verlieren (z. B.u64 as u32).
// Schlecht: implizite Paniclet value = some_option.unwrap();
// Gut: explizite Verarbeitunglet value = some_option.unwrap_or_else(|| { log::error!("Missing expected value"); default_value()});2. Ownership und Borrowing: Typische Fehler
Das Ownership-System ist die Hauptbesonderheit von Rust. Compiler-Fehler sind hier die besten Freunde, aber Code-Review hilft, logische Fehler zu finden, die der Compiler nicht abfängt.
Übermäßiges Klonen
Ein häufiges Problem ist das Klonen, wo eine Referenz übergeben werden könnte. Dies reduziert die Leistung und erhöht die Speichernutzung.
// Schlecht: Klonen des gesamten Stringsfn process(data: Vec<String>) { for item in data { let cloned = item.clone(); do_something(&cloned); }}
// Gut: Übergabe einer Referenzfn process(data: &[String]) { for item in data { do_something(item); }}Unnötige Ausleihen und Lifetimes
Prüfen Sie, ob die Lebensdauer von Referenzen nicht zu lang ist. Manchmal kann man die Funktionssignatur vereinfachen, indem man überflüssige Lifetime-Annotationen entfernt.
// Komplex: überflüssige Lifetimesfn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}
// Wenn