Borrowing
Compilerfehler: Unveränderliches Slice sortieren
Abschnitt betitelt „Compilerfehler: Unveränderliches Slice sortieren“Programm 1:
/// Gibt das n-te größte Element in einem Slice zurückfn find_nth<T: Ord + Clone>(elems: &[T], n: usize) -> T { elems.sort(); let t = &elems[n]; return t.clone();}Wenn Sie versuchen würden, dieses Programm zu kompilieren, welche der folgenden Beschreibungen trifft am besten auf den Compilerfehler zu, den Sie erhalten würden?
Die Methode slice::sort erwartet eine veränderliche Referenz auf ein Slice, erhält aber stattdessen eine unveränderliche Referenz.
Speichersicherheit nach Compilerfehler
Abschnitt betitelt „Speichersicherheit nach Compilerfehler“Programm 1:
/// Gibt das n-te größte Element in einem Slice zurückfn find_nth<T: Ord + Clone>(elems: &[T], n: usize) -> T { elems.sort(); let t = &elems[n]; return t.clone();}Normalerweise gibt der Compiler, wenn Sie versuchen, diese Funktion zu kompilieren, den folgenden Fehler zurück:
error[E0596]: kann `*elems` nicht als veränderlich borgen, da es sich hinter einer `&`-Referenz befindet --> test.rs:3:5 |3 | elems.sort(); | ^^^^^^^^^^^^ `elems` ist eine `&`-Referenz, daher können die Daten, auf die sie verweist, nicht als veränderlich geborgt werdenNehmen Sie an, der Compiler hätte diese Funktion NICHT abgelehnt. Welche (falls zutreffend) der folgenden Programme würden (1) den Compiler passieren und (2) möglicherweise undefiniertes Verhalten verursachen, wenn sie ausgeführt werden? Markieren Sie jedes Programm, das beide Kriterien erfüllt, ODER markieren Sie „Keines dieser Programme“, wenn keines davon zutrifft.
Dieses Programm ist technisch speichersicher, da slice::sort nur Elemente verschieben, aber nicht freigeben kann. Zum Beispiel zeigt &v[0] nach dem Aufruf von find_nth garantiert auf eine Zahl, auch wenn es nicht die ursprüngliche Zahl ist.
Beachten Sie, dass find_nth(&v, 10) kein undefiniertes Verhalten verursacht, da Rust Überprüfungen bei Array-Zugriffen durchführt, sodass die Auswertung von &v[10] zu einem Panic führen wird.
Korrektur des Sortierfehlers
Abschnitt betitelt „Korrektur des Sortierfehlers“Programm 1:
/// Gibt das n-te größte Element in einem Slice zurückfn find_nth<T: Ord + Clone>(elems: &[T], n: usize) -> T { elems.sort(); let t = &elems[n]; return t.clone();}Welche der folgenden Korrekturen (gelb hervorgehoben) erfüllt diese drei Kriterien am besten: 1. Die korrigierte Funktion besteht den Rust-Compiler, 2. Die korrigierte Funktion bewahrt die Absicht des ursprünglichen Codes und 3. Die korrigierte Funktion führt keine unnötigen Ineffizienzen ein?
Eine Funktion wie find_nth ist eindeutig als schreibgeschützte Funktion gedacht, d.h. um eine Eigenschaft der Eingabesequenz zu extrahieren. Jede Lösung, die die Eingabe verändert oder verwirft, bewahrt daher nicht die ursprüngliche Absicht der Funktion, selbst wenn sie effizienter ist als das Erstellen eines Hilfsvektors.
Das Erstellen eines Vec<&T> ist dem Erstellen eines Vec<T> vorzuziehen, da elems.to_vec() teuer sein könnte, wenn T groß ist. Wüssten wir jedoch, dass T: Copy ist, wäre to_vec vorzuziehen, um die Anzahl der Pointer-Dereferenzen innerhalb von elems.sort() zu reduzieren.
Borrow-Checker Konflikt
Abschnitt betitelt „Borrow-Checker Konflikt“Programm 2:
struct TestResult { /// Testergebnisse der Studenten scores: Vec<usize>,
/// Ein möglicher Wert, um alle Ergebnisse anzupassen curve: Option<usize>}impl TestResult { pub fn get_curve(&self) -> &Option<usize> { &self.curve }
/// Wenn es eine Anpassung gibt, werden alle /// Ergebnisse um diesen Wert erhöht pub fn apply_curve(&mut self) { if let Some(curve) = self.get_curve() { for score in self.scores.iter_mut() { *score += *curve; } } }}Wenn Sie versuchen würden, dieses Programm zu kompilieren, welche der folgenden Beschreibungen trifft am besten auf den Compilerfehler zu, den Sie erhalten würden?
Aufgrund der Lebenszeit-Elision hat die Funktion get_curve die Typsignatur get_curve<'a>(&'a self) -> &'a Option<usize>. Dies bedeutet, dass ein Aufruf von self.get_curve() das gesamte Borgen von self erweitert, nicht nur das von self.curve. Daher ist self innerhalb des Bereichs von let Some(curve) = ... unveränderlich geborgt, und self.scores.iter_mut() kann nicht aufgerufen werden.
Speichersicherheit bei Borrowing
Abschnitt betitelt „Speichersicherheit bei Borrowing“Programm 2:
struct TestResult { /// Testergebnisse der Studenten scores: Vec<usize>,
/// Ein möglicher Wert, um alle Ergebnisse anzupassen curve: Option<usize>}impl TestResult { pub fn get_curve(&self) -> &Option<usize> { &self.curve }
/// Wenn es eine Anpassung gibt, werden alle /// Ergebnisse um diesen Wert erhöht pub fn apply_curve(&mut self) { if let Some(curve) = self.get_curve() { for score in self.scores.iter_mut() { *score += *curve; } } }}Normalerweise gibt der Compiler, wenn Sie versuchen, diese Funktion zu kompilieren, den folgenden Fehler zurück:
error[E0502]: kann `self.scores` nicht als veränderlich borgen, da es auch als unveränderlich geborgt ist --> test.rs:17:26 |16 | if let Some(curve) = self.get_curve() { | ---------------- unveränderliches Borgen tritt hier auf17 | for score in self.scores.iter_mut() { | ^^^^^^^^^^^^^^^^^^^^^^ veränderliches Borgen tritt hier auf18 | *score += *curve; | ------ unveränderliches Borgen wird später hier verwendetNehmen Sie an, der Compiler hätte diese Funktion NICHT abgelehnt. Welche (falls zutreffend) der folgenden Programme würden (1) den Compiler passieren und (2) möglicherweise undefiniertes Verhalten verursachen, wenn sie ausgeführt werden? Markieren Sie jedes Programm, das beide Kriterien erfüllt, ODER markieren Sie „Keines dieser Programme“, wenn keines davon zutrifft.
Dieses Programm ist, wie geschrieben, tatsächlich sicher. Es ist eine Einschränkung des Borrow-Checkers, nicht zu verstehen, dass get_curve nur curve borgt und scores nicht beeinflusst. Theoretisch könnte jedoch die Speichersicherheit verletzt werden, wenn get_curve geändert würde, um eine Referenz auf etwas mit self.scores zurückzugeben.
Borrow-Checker Korrektur
Abschnitt betitelt „Borrow-Checker Korrektur“Programm 2:
struct TestResult { /// Testergebnisse der Studenten scores: Vec<usize>,
/// Ein möglicher Wert, um alle Ergebnisse anzupassen curve: Option<usize>}impl TestResult { pub fn get_curve(&self) -> &Option<usize> { &self.curve }
/// Wenn es eine Anpassung gibt, werden alle /// Ergebnisse um diesen Wert erhöht pub fn apply_curve(&mut self) { if let Some(curve) = self.get_curve() { for score in self.scores.iter_mut() { *score += *curve; } } }}Welche der folgenden Korrekturen (gelb hervorgehoben) erfüllt diese drei Kriterien am besten: 1. Die korrigierte Funktion besteht den Rust-Compiler, 2. Die korrigierte Funktion bewahrt die Absicht des ursprünglichen Codes und 3. Die korrigierte Funktion führt keine unnötigen Ineffizienzen ein?
Durch das Inlining der Definition von get_curve in apply_curve versteht der Borrow-Checker, dass self.curve nicht self.scores ist, und erlaubt somit die Kompilierung der Funktion. Dies ist eine gängige Umgehung für diese Art von Borrow-Checker-Einschränkung.
Eine weitere Möglichkeit besteht darin, die Tatsache zu nutzen, dass self.curve günstig zu kopieren ist und Option::copied zu verwenden, wodurch das Borgen von self sofort nach dem Aufruf von .copied() freigegeben würde.