Bewegungssemantik in Rust

Nilesh Katuwal 7 Juni 2022
Bewegungssemantik in Rust

In diesem Artikel lernen wir die Bewegungssemantik in Rust kennen.

Bewegungssemantik in Rust

In Rust sind alle Typen veränderbar, und alle Verschiebungsoperationen entsprechen einer Bitkopie der Originaldaten am neuen Speicherort. Move konvertiert alle erfassten Variablen per Referenz oder veränderlicher Referenz in erfasste Variablen nach Wert.

Wenn Threads beteiligt sind, wird häufig die Bewegung verwendet.

Rustentfernungsprozesse sind ebenfalls destruktiv. Nachdem Sie beispielsweise eine Variable verlassen haben, kann die Variable nicht mehr im Code verwendet werden.

In Rust wird ein Move konsequent umgesetzt, indem die Daten im Objekt selbst kopiert und das Originalobjekt nicht zerstört wird. Es wird niemals durch Kopieren der verwalteten Ressourcen des Objekts oder Ausführen von benutzerdefiniertem Code implementiert.

Rust macht eine Ausnahme für Typen, die keine Bewegungssemantik erfordern. Stattdessen enthält der Wert alle notwendigen Informationen, um ihn darzustellen, und es werden keine Heap-Zuweisungen oder Ressourcen durch den Wert verwaltet, wie z. B. i32.

Diese Typen implementieren die spezielle Eigenschaft copy, da das Kopieren kostengünstig und die Standardmethode zum Übergeben an Funktionen oder zum Verwalten von Zuweisungen ist:

fn foo(bar: i32) {
    // Implementation
}

let var: i32 = 9;
foo(var); // copy
foo(var); // copy
foo(var); // copy

Der Standard-Funktionsaufruf verwendet Move-Semantik für andere Typen als Copy, wie z. B. String. Wenn in Rust eine Variable verschoben wird, endet ihre Lebensdauer vorzeitig.

Zur Kompilierzeit ersetzt die Verschiebung den Destruktoraufruf am Ende des Blocks, daher ist es ein Fehler, den folgenden Code für String zu schreiben:

fn foo(bar: String) {
    // Implementation
}

let var: String = "Hello".to_string();
foo(var); // Move
foo(var); // Compile-Time Error
foo(var); // Compile-Time Error

Clone rust erfordert Implementierungen, ist aber für alle Moves identisch: Kopieren Sie den Speicher in den Wert und rufen Sie nicht den Destruktor des ursprünglichen Werts auf.

Und in Rust sind mit genau dieser Implementierung alle Typen beweglich; nicht verschiebbare Typen existieren nicht (allerdings nicht verschiebbare Werte). Die Bytes müssen Informationen über die Ressource, die der Wert verwaltet, wie z. B. einen Zeiger, am neuen Speicherort genauso effektiv codieren wie am vorherigen Speicherort.

Rust verfügt über einen als Pinning bekannten Mechanismus, der im Typsystem anzeigt, dass ein bestimmter Wert nie wieder verschoben wird, der zur Implementierung selbstreferenzieller Werte verwendet werden kann und asynchron verwendet wird.

Beispiel:

fn destroy_box(z: Box<i32>) {
    println!("The box that contains {} is destroyed", z);
}

fn main() {
    let a = 5u32;
    let b = a;
    println!("a is {}, and y is {}", a, b);

    let m = Box::new(5i32);

    println!("a contains: {}", m);
    let n = m;
    destroy_box(n);
}

Die Funktion destroy_box übernimmt den Heap-zugewiesenen Speicher im obigen Quellcode. Zuerst wird das z zerstört und der Speicher freigegeben.

m ist ein Zeiger auf eine dem Heap zugeordnete Ganzzahl.

Ausgabe:

a is 5, and y is 5
a contains: 5
The box that contains 5 is destroyed