Zum Inhalt springen

Borrowing

Programm 1:

/// Gibt das n-te größte Element in einem Slice zurück
fn 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.


Programm 1:

/// Gibt das n-te größte Element in einem Slice zurück
fn 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 werden

Nehmen 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.


Programm 1:

/// Gibt das n-te größte Element in einem Slice zurück
fn 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.


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.


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 auf
17 | for score in self.scores.iter_mut() {
| ^^^^^^^^^^^^^^^^^^^^^^ veränderliches Borgen tritt hier auf
18 | *score += *curve;
| ------ unveränderliches Borgen wird später hier verwendet

Nehmen 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.


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.