Zum Inhalt springen

Borrow-Checker

Angenommen, Sie schreiben eine Funktion mit der folgenden Spezifikation:

round_all nimmt eine Liste von Gleitkommazahlen als Eingabe und rundet jede Zahl direkt (in-place) auf die nächste ganze Zahl.

Welche der folgenden ist die am besten geeignete Typensignatur für eine Funktion, die diese Spezifikation implementiert?

Die Spezifikation erfordert, dass die Eingabe direkt (in-place) mutiert wird. Daher akzeptiert die am besten geeignete Typensignatur eine mutable Referenz auf die Eingabe. Eine immutable Referenz oder ein übernommener Vektor sind für diese Spezifikation beide ungeeignet.


Angenommen, Sie schreiben eine Funktion mit der folgenden Spezifikation:

find_contains nimmt eine Sammlung von Strings und einen Ziel-Substring als Eingabe. Sie gibt eine Liste aller Strings in der Sammlung zurück, die den Ziel-Substring enthalten.

Welche der folgenden ist die am besten geeignete Typensignatur für eine Funktion, die diese Spezifikation implementiert?

Für haystack kann der Slice-Typ &[String] mehr Eingaben akzeptieren als &Vec<String> , daher wird er bevorzugt. Für needle muss der Ziel-Substring nicht auf dem Heap allokiert werden, daher wird &str gegenüber String bevorzugt. Für den Rückgabetyp ist Vec<String> nicht wünschenswert, da dies ein Klonen der Eingabe-Strings erfordern würde. &[String] ist nicht wünschenswert, da es nur eine zusammenhängende Teilsequenz der Eingabe zurückgeben kann. Vec<&String> ist am wünschenswertesten, da es nur die Kosten für die Allokation des Vektors verursacht, nicht die der Strings selbst.


Rust verbietet normalerweise mehrere mutable Zugriffe auf dasselbe Array, selbst wenn diese Zugriffe disjunkt sind. Zum Beispiel kompiliert diese Funktion nicht:

fn main() {
let mut v = vec![0, 1, 2, 3];
let (r0, r1) = (&mut v[0..2], &mut v[2..4]);
r0[0] += 1;
r1[0] += 1;
}

Die Rust-Standardbibliothek verfügt jedoch über eine Funktion slice::split_at_mut , die diese Funktionalität erlaubt:

fn main() {
let mut v = vec![0, 1, 2, 3];
let (r0, r1) = v.split_at_mut(2);
r0[0] += 1;
r1[0] += 1;
}

Welche der folgenden Aussagen beschreibt am besten, wie split_at_mut implementiert werden kann?

Wie in Kapitel 4.3 „Fixing a Safe Program: Mutating Different Array Elements“ besprochen, werden Funktionen wie split_at_mut mit der unsafe -Funktion implementiert. Diese Funktion deaktiviert den Borrow-Checker nicht vollständig, sondern ermöglicht die Verwendung spezifischer unsicherer Funktionen wie Rohzeiger.


Betrachten Sie die Berechtigungen im folgenden Programm:

#fn main() {
let s = String::new();
let s_ref = &s;`(focus,paths:*s_ref)`
#println!("{s_ref}");
#}

Welche der folgenden Aussagen erklärt am besten, warum *s_ref nicht die Own (Ownership)-Berechtigung besitzt?

Die Own-Berechtigung repräsentiert die Ownership eines Objekts. Es kann nur einen Owner eines Objekts geben, daher ist es wichtig, dass Referenzen die Ownership von nicht-kopierbaren Typen wie String nicht übertragen können. Wenn zwei Variablen dächten, sie besäßen denselben String, würden beide versuchen, ihn freizugeben, was zu einem Double-Free führen würde.


Betrachten Sie die Menge der Rust-Programme, die keinen unsafe -Code enthalten. Wählen Sie jede der folgenden Aussagen aus, die über die Arten von Programmen zutrifft, die vom Borrow-Checker akzeptiert und abgelehnt werden:

Der Borrow-Checker lehnt immer Programme mit undefiniertem Verhalten ab, kann aber manchmal Programme ohne undefiniertes Verhalten (d.h. die völlig sicher sind) ablehnen. Technisch ausgedrückt ist der Borrow-Checker eine solide und unvollständige Analyse.


Die Funktion extract wird vom Borrow-Checker abgelehnt:

fn extract(b: &Box<i32>) -> i32 {
let b2: Box<i32> = *b;
*b2
}

Stellen Sie sich vor, der Borrow-Checker hätte diese Funktion nicht abgelehnt. Bestimmen Sie, ob es eine Eingabe gibt, bei der die Funktion undefiniertes Verhalten verursachen würde, wenn sie mit dieser Eingabe ausgeführt wird.

Diese Funktion würde bei jeder Eingabe einen Double-Free verursachen.


Die Funktion transfer_string wird vom Borrow-Checker abgelehnt:

fn get_first(strs: &mut (String, String)) -> &mut String {
&mut strs.0
}
fn get_second(strs: &mut (String, String)) -> &mut String {
&mut strs.1
}
fn transfer_string(strs: &mut (String, String)) {
let fst = get_first(strs);
let snd = get_second(strs);
fst.push_str(snd);
snd.clear();
}

Stellen Sie sich vor, der Borrow-Checker hätte diese Funktion nicht abgelehnt. Bestimmen Sie, ob es eine Eingabe gibt, bei der die Funktion undefiniertes Verhalten verursachen würde, wenn sie mit dieser Eingabe ausgeführt wird.

Der Borrow-Checker lehnt diese Funktion ab, weil er davon ausgeht, dass get_first und get_second eine mutable Referenz auf eine der Komponenten des Tupels zurückgeben könnten, und somit fst und snd möglicherweise auf denselben Wert zeigen könnten. Aber sie sind in diesem Programm immer unterschiedlich, daher ist diese Funktion tatsächlich sicher.