Referenzen und Entleihen

RustRustBeginner
Jetzt üben

This tutorial is from open-source community. Access the source code

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

Willkommen zu References and Borrowing. Dieser Lab ist ein Teil des Rust Buchs. Du kannst deine Rust-Fähigkeiten in LabEx üben.

In diesem Lab lernen wir, wie wir in Rust Referenzen verwenden, um Werte zu entleihen, anstatt die Eigentumsgewalt zu übernehmen. Dadurch können wir Daten übergeben und manipulieren, ohne die Eigentumsgewalt an die aufrufende Funktion zurückgeben zu müssen.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/MemorySafetyandManagementGroup(["Memory Safety and Management"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/BasicConceptsGroup -.-> rust/mutable_variables("Mutable Variables") rust/DataTypesGroup -.-> rust/string_type("String Type") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/MemorySafetyandManagementGroup -.-> rust/lifetime_specifiers("Lifetime Specifiers") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") subgraph Lab Skills rust/variable_declarations -.-> lab-100393{{"Referenzen und Entleihen"}} rust/mutable_variables -.-> lab-100393{{"Referenzen und Entleihen"}} rust/string_type -.-> lab-100393{{"Referenzen und Entleihen"}} rust/function_syntax -.-> lab-100393{{"Referenzen und Entleihen"}} rust/expressions_statements -.-> lab-100393{{"Referenzen und Entleihen"}} rust/lifetime_specifiers -.-> lab-100393{{"Referenzen und Entleihen"}} rust/method_syntax -.-> lab-100393{{"Referenzen und Entleihen"}} end

References and Borrowing

Das Problem mit dem Tupel-Code in Listing 4-5 besteht darin, dass wir die String an die aufrufende Funktion zurückgeben müssen, damit wir die String auch nach dem Aufruf von calculate_length noch verwenden können, da die String in calculate_length bewegt wurde. Stattdessen können wir eine Referenz auf den String-Wert bereitstellen. Eine Referenz ist wie ein Zeiger, insofern es eine Adresse ist, die wir folgen können, um auf die an dieser Adresse gespeicherten Daten zuzugreifen; diese Daten werden von einer anderen Variable besessen. Im Gegensatz zu einem Zeiger ist garantiert, dass eine Referenz während ihrer Lebensdauer auf einen gültigen Wert eines bestimmten Typs zeigt.

Hier ist, wie Sie eine calculate_length-Funktion definieren und verwenden würden, die eine Referenz auf ein Objekt als Parameter hat, anstatt die Eigentumsgewalt über den Wert zu übernehmen:

Dateiname: src/main.rs

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

Zunächst bemerken Sie, dass der gesamte Tupel-Code in der Variablendeklaration und im Funktionsrückgabewert fehlt. Zweitens bemerken Sie, dass wir &s1 an calculate_length übergeben und in seiner Definition &String statt String verwenden. Diese Ampersands stellen Referenzen dar, und sie ermöglichen es Ihnen, auf einen Wert zu verweisen, ohne die Eigentumsgewalt darüber zu erwerben. Abbildung 4-5 veranschaulicht diesen Begriff.

Abbildung 4-5: Ein Diagramm von &String s, das auf String s1 zeigt

Hinweis: Das Gegenteil von der Referenzierung mit & ist die Dereferenzierung, die mit dem Dereferenzierungsoperator * erreicht wird. Wir werden einige Anwendungen des Dereferenzierungsoperators im Kapitel 8 sehen und die Details der Dereferenzierung im Kapitel 15 diskutieren.

Schauen wir uns den Funktionsaufruf hier genauer an:

let s1 = String::from("hello");

let len = calculate_length(&s1);

Die Syntax &s1 ermöglicht es uns, eine Referenz zu erstellen, die auf den Wert von s1 verweist, aber ihn nicht besitzt. Da sie ihn nicht besitzt, wird der Wert, auf den sie zeigt, nicht gelöscht, wenn die Referenz nicht mehr verwendet wird.

Ebenso verwendet die Signatur der Funktion &, um anzuzeigen, dass der Typ des Parameters s eine Referenz ist. Fügen wir einige erklärende Anmerkungen hinzu:

fn calculate_length(s: &String) -> usize { // s ist eine Referenz auf eine String
    s.len()
} // Hier geht s außer Gültigkeitsbereich. Aber da es keine Eigentumsgewalt über das hat,
  // auf das es verweist, wird die String nicht gelöscht

Der Gültigkeitsbereich, in dem die Variable s gültig ist, ist der gleiche wie der eines beliebigen Funktionsparameters, aber der Wert, auf den die Referenz zeigt, wird nicht gelöscht, wenn s nicht mehr verwendet wird, weil s keine Eigentumsgewalt hat. Wenn Funktionen Referenzen als Parameter statt der tatsächlichen Werte haben, müssen wir die Werte nicht zurückgeben, um die Eigentumsgewalt zurückzugeben, weil wir nie die Eigentumsgewalt hatten.

Wir nennen die Aktion des Erstellens einer Referenz Entleihen. Wie im realen Leben können Sie etwas von jemandem entleihen, wenn er es besitzt. Wenn Sie fertig sind, müssen Sie es zurückgeben. Sie besitzen es nicht.

Was passiert also, wenn wir versuchen, etwas zu modifizieren, das wir entleihen? Versuchen Sie den Code in Listing 4-6. Vorabentschluß: Es funktioniert nicht!

Dateiname: src/main.rs

fn main() {
    let s = String::from("hello");

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}

Listing 4-6: Versuch, einen entlehnten Wert zu modifizieren

Hier ist der Fehler:

error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&`
reference
 --> src/main.rs:8:5
  |
7 | fn change(some_string: &String) {
  |                        ------- help: consider changing this to be a mutable
reference: `&mut String`
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `some_string` is a `&` reference, so
the data it refers to cannot be borrowed as mutable

Genau wie Variablen sind Referenzen standardmäßig unveränderlich. Wir dürfen etwas, auf das wir eine Referenz haben, nicht modifizieren.

Mutable Referenzen

Wir können den Code aus Listing 4-6 beheben, um es uns zu ermöglichen, einen entlehnten Wert zu modifizieren, indem wir nur einige kleine Änderungen vornehmen, die stattdessen eine mutable Referenz verwenden:

Dateiname: src/main.rs

fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

Zunächst ändern wir s zu mut. Dann erstellen wir eine mutable Referenz mit &mut s, wo wir die change-Funktion aufrufen, und aktualisieren die Funktionssignatur, um eine mutable Referenz mit some_string: &mut String zu akzeptieren. Dies macht deutlich, dass die change-Funktion den Wert mutieren wird, den sie entleiht.

Mutable Referenzen haben eine große Einschränkung: Wenn Sie eine mutable Referenz zu einem Wert haben, können Sie keine anderen Referenzen zu diesem Wert haben. Dieser Code, der versucht, zwei mutable Referenzen zu s zu erstellen, wird fehlschlagen:

Dateiname: src/main.rs

let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s;

println!("{r1}, {r2}");

Hier ist der Fehler:

error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
6 |
7 |     println!("{r1}, {r2}");
  |                -- first borrow later used here

Dieser Fehler sagt aus, dass dieser Code ungültig ist, weil wir s nicht mehr als einmal als mutable entleihen können. Die erste mutable Entleihe ist in r1 und muss bis zu ihrer Verwendung in der println! bestehen, aber zwischen der Erstellung dieser mutable Referenz und ihrer Verwendung haben wir versucht, eine weitere mutable Referenz in r2 zu erstellen, die das gleiche Daten wie r1 entleiht.

Die Einschränkung, die es verhindert, dass gleichzeitig mehrere mutable Referenzen zu den gleichen Daten existieren, ermöglicht die Mutation, aber in einem sehr kontrollierten Verfahren. Dies ist etwas, mit dem sich neue Rustaceans schwer tun, weil in den meisten Sprachen Sie jederzeit mutieren können. Der Vorteil dieser Einschränkung ist, dass Rust Laufzeitfehler bei der Kompilierung verhindern kann. Ein Datenkonflikt ähnelt einem Wettlaufbedingung und tritt auf, wenn diese drei Verhaltensweisen auftreten:

  • Zwei oder mehr Zeiger greifen gleichzeitig auf die gleichen Daten zu.
  • Mindestens ein Zeiger wird verwendet, um auf die Daten zu schreiben.
  • Es gibt kein Mechanismus, um den Zugang zu den Daten zu synchronisieren.

Datenkonflikte verursachen undefiniertes Verhalten und können schwierig zu diagnostizieren und zu beheben sein, wenn Sie versuchen, sie zur Laufzeit aufzuspüren; Rust verhindert dieses Problem, indem es Code mit Datenkonflikten nicht kompilieren lässt!

Wie immer können wir geschweifte Klammern verwenden, um einen neuen Gültigkeitsbereich zu erstellen, was mehrere mutable Referenzen ermöglicht, nur keine gleichzeitigen:

let mut s = String::from("hello");

{
    let r1 = &mut s;
} // r1 geht hier außer Gültigkeitsbereich, so dass wir eine neue Referenz problemlos erstellen können

let r2 = &mut s;

Rust durchsetzt eine ähnliche Regel für die Kombination von mutable und immutable Referenzen. Dieser Code führt zu einem Fehler:

let mut s = String::from("hello");

let r1 = &s; // kein Problem
let r2 = &s; // kein Problem
let r3 = &mut s; // GROßES PROBLEM

println!("{r1}, {r2}, and {r3}");

Hier ist der Fehler:

error[E0502]: cannot borrow `s` as mutable because it is also borrowed as
immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &s; // no problem
  |              -- immutable borrow occurs here
5 |     let r2 = &s; // no problem
6 |     let r3 = &mut s; // GROßES PROBLEM
  |              ^^^^^^ mutable borrow occurs here
7 |
8 |     println!("{r1}, {r2}, and {r3}");
  |                -- immutable borrow later used here

Puh! Wir können auch keine mutable Referenz haben, wenn wir eine immutable Referenz zu demselben Wert haben.

Anwender einer immutable Referenz erwarten nicht, dass der Wert plötzlich von ihnen wegändert wird! Allerdings sind mehrere immutable Referenzen erlaubt, weil niemand, der nur die Daten liest, die Möglichkeit hat, das Lesen anderer Personen zu beeinflussen.

Beachten Sie, dass der Gültigkeitsbereich einer Referenz ab dem Punkt beginnt, an dem sie eingeführt wird, und bis zum letzten Mal, an dem diese Referenz verwendet wird. Beispielsweise wird dieser Code kompilieren, weil der letzte Gebrauch der immutable Referenzen, die println!, vor der Einführung der mutable Referenz erfolgt:

let mut s = String::from("hello");

let r1 = &s; // kein Problem
let r2 = &s; // kein Problem
println!("{r1} and {r2}");
// Variablen r1 und r2 werden nach diesem Punkt nicht mehr verwendet

let r3 = &mut s; // kein Problem
println!("{r3}");

Die Gültigkeitsbereiche der immutable Referenzen r1 und r2 enden nach der println!, wo sie zuletzt verwendet werden, was vor der Erstellung der mutable Referenz r3 erfolgt. Diese Gültigkeitsbereiche überlappen sich nicht, daher ist dieser Code erlaubt: Der Compiler kann erkennen, dass die Referenz vor dem Ende des Gültigkeitsbereichs nicht mehr verwendet wird.

Auch wenn die Entleiherrors manchmal frustrierend sein können, denken Sie daran, dass es der Rust-Compiler ist, der frühzeitig (bei der Kompilierung statt bei der Laufzeit) ein potenzielles Bug aufzeigt und Ihnen genau anzeigt, wo das Problem ist. Dann müssen Sie nicht ermitteln, warum Ihre Daten nicht so sind, wie Sie dachten.

Hängende Referenzen

In Sprachen mit Zeigern ist es leicht, versehentlich einen hängenden Zeiger zu erstellen - einen Zeiger, der auf einen Speicherort verweist, der möglicherweise an jemand anderen gegeben wurde - indem man einen Teil des Speichers freigibt, während ein Zeiger auf diesen Speicherort erhalten bleibt. Im Gegensatz dazu gewährleistet der Rust-Compiler, dass Referenzen niemals hängende Referenzen sein werden: Wenn Sie eine Referenz auf einige Daten haben, wird der Compiler sicherstellen, dass die Daten nicht außer Gültigkeitsbereich gelangen, bevor die Referenz auf die Daten außer Gültigkeitsbereich geht.

Lassen Sie uns versuchen, eine hängende Referenz zu erstellen, um zu sehen, wie Rust sie mit einem Kompilierfehler verhindert:

Dateiname: src/main.rs

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

Hier ist der Fehler:

error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value,
but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
  |
5 | fn dangle() -> &'static String {
  |                ~~~~~~~~

Diese Fehlermeldung bezieht sich auf eine Funktion, die wir noch nicht behandelt haben: Lebensdauern. Wir werden Lebensdauern im Kapitel 10 im Detail diskutieren. Wenn Sie jedoch die Teile über Lebensdauern außer Acht lassen, enthält die Meldung den Schlüssel zu dem, warum dieser Code ein Problem ist:

this function's return type contains a borrowed value, but there
is no value for it to be borrowed from

Schauen wir uns genauer an, was genau in jeder Phase unseres dangle-Codes geschieht:

// src/main.rs
fn dangle() -> &String { // dangle gibt eine Referenz auf eine String zurück

    let s = String::from("hello"); // s ist eine neue String

    &s // wir geben eine Referenz auf die String, s zurück
} // Hier geht s außer Gültigkeitsbereich und wird gelöscht, also geht auch sein Speicher verloren
  // Gefahr!

Da s innerhalb von dangle erstellt wird, wird s deallokiert, wenn der Code von dangle abgeschlossen ist. Aber wir haben versucht, eine Referenz darauf zurückzugeben. Das bedeutet, dass diese Referenz auf einen ungültigen String verweisen würde. Das geht nicht! Rust lässt uns das nicht tun.

Die Lösung hier besteht darin, den String direkt zurückzugeben:

fn no_dangle() -> String {
    let s = String::from("hello");

    s
}

Dies funktioniert ohne Probleme. Die Eigentumsgewalt wird übergeben, und nichts wird deallokiert.

Die Regeln von Referenzen

Fassen wir zusammen, was wir über Referenzen diskutiert haben:

  • Zu einem bestimmten Zeitpunkt können Sie entweder eine mutable Referenz oder beliebig viele immutable Referenzen haben.
  • Referenzen müssen immer gültig sein.

Als nächstes werden wir uns eine andere Art von Referenzen ansehen: Slices.

Zusammenfassung

Herzlichen Glückwunsch! Sie haben das Lab zu References and Borrowing abgeschlossen. Sie können in LabEx weitere Labs absolvieren, um Ihre Fähigkeiten zu verbessern.