Programmiersprache Rust 1.63 bekommt Threads, die sich Daten ausleihen dürfen

Neben den Scoped Threads bringt Version 1.63 der Programmiersprache Wrapper für Datei-Handles beziehungsweise -deskriptoren zum Klären der Ownership.

Lesezeit: 3 Min.
In Pocket speichern
vorlesen Druckansicht Kommentare lesen 22 Beiträge
Von
  • Rainald Menge-Sonnentag

Im planmäßigen Sechswochentakt ist Rust 1.63 erschienen. Das Release bringt gleich zwei Änderungen, die mit dem Ownership-Konzept von Rust zusammenhängen. Zum einen erlauben Scoped Threads einen flexibleren Umgang mit den Daten für einen Thread und zum anderen lassen sich Dateideskriptoren beziehungsweise -Handles nun mit einem Wrapper versehen, der festlegt, ob sie Eigentum oder ausgeliehen sind.

Auch wenn Rust das Async-Await-Pattern erst Ende 2019 eingeführt hat, kennt die Programmiersprache bereits seit ihren Anfängen Threads für die nebenläufige Programmierung. Bisher war das Closure für das Erstellen eines Threads mit der Standard-Library über std::thread::spawn mit 'static gekennzeichnet. Die damit markierte statische Lifetime legt fest, dass der Thread die Ownership über alle Parameter in ihrer Closure haben muss. Es ist somit nicht möglich, geborgte (Borrowed) Daten an einen Thread zu übergeben.

Die API std::thread::scope erstellen nun einen Ausführungskontext (Scope), um sogenannte Scoped Threads zu spawnen. Der Kontext garantiert, dass alle darin befindlichen Threads am Ende entweder manuell über join oder automatisch vor dem Verlassen der Funktion zusammengeführt werden. Daher sind Borrows von nicht-'static-Daten an Scoped Threads erlaubt. Folgendes Beispiel aus dem Rust-Blog veranschaulicht die Scoped Threads im ersten Teil mit unveränderlichen und im zweiten mit veränderlichen Borrows:

let mut a = vec![1, 2, 3];
let mut x = 0;

std::thread::scope(|s| {
    s.spawn(|| {
        println!("hello from the first scoped thread");
        // We can borrow `a` here.
        dbg!(&a);
    });
    s.spawn(|| {
        println!("hello from the second scoped thread");
        // We can even mutably borrow `x` here,
        // because no other threads are using it.
        x += a[0] + a[2];
    });
    println!("hello from the main thread");
});

// After the scope, we can modify and access our variables again:
a.push(4);
assert_eq!(x, a.len());

Eine weitere Neuerung betrifft den Umgang mit Dateien und bezieht sich ebenfalls auf das Ownership-Konzept. Bisher hat Rust für vom Betriebssystem übernommene Dateideskriptoren unter Unix beziehungsweise -Handles unter Windows eine plattformspezifische Repräsentation übernommen. Bei RawFd war aber nicht klar, ob die API Eigentümer des Deskriptors ist oder ihn nur ausgeliehen hat.

Rust 1.63 führt zusätzliche Wrapper ein, die explizit die Eigentumsverhältnisse festlegen: Ein BorrowedFd ist gliehen, während OwnedFd die Ownership markiert. Für Letzteren schließt das Programm den Deskriptor beim Freigeben.

Daneben erweitert das Release den Umgang mit Mutexes und Konstrukten zum Verwalten von Locks in der asynchronen Programmierung. Seit Version 1.62 setzt Rust unter Linux auf eine futex-basierte Implementierung statt wie zuvor auf POSIX Threads. Der Aufruf von Condvar::new, Mutex::new und RwLock::new ist neuerdings im const-Kontext erlaubt.

Schließlich gelten die Non-lexical Lifetimes (NLL) für Borrows nun als stabil. Sie waren zwar bereits Bestandteil von Rust 2018, galten allerdings bisher nur für die Editionen 2018 und 2021, nicht für Rust 2015. Das ändert sich mit dem aktuellen Release. Details dazu finden sich in einem Blogbeitrag von Anfang August.

Ownership in Rust: Eindeutig geklärter Besitz

Eine der Stärken von Rust ist die Speichersicherheit: Das Ownership-Konzept der Programmiersprache legt strikte Regeln fest, die typische Speicherfehler verhindern, die in Sprachen wie C oder C++ auftreten. Rust hat drei Onwership-Regeln: Jeder Wert hat einen Besitzer (Owner). Es kann immer nur einen Besitzer zu einem Zeitpunkt geben. Und wenn der Besitzer nicht mehr im Ausführungskontext ist, wird der Wert fallengelassen.

Reserviert wird der benötigte Speicher beim Initialisieren der Variable. Weil es nur einen Besitzer geben darf, erfordert die Zuweisung einer Variable an eine andere ein anderes Vorgehen als bei anderen Programmiersprachen. Ein Weg, ist Werte zu klonen, womit sie doppelt vorhanden sind, doppelt so viel Speicher benötigen und eine Änderung nur für den jeweiligen Besitzer gilt.

Mit Referenzen ist es möglich, den eigentlichen Wert weiterzugeben, der standardmäßig unveränderbar (Unmutable) ist, also nur den Lesezugriff erlaubt. Solche Referenzen dürfen mehrfach existieren. Schließlich existieren mit &mut gekennzeichnete veränderbare (Mutable) Referenzen, die ein Exklusivrecht sowohl zum Schreiben als auch zum Lesen haben. Sie leihen (Borrow) sich das Recht zum Zugriff vom Owner. Damit dürfen auch keine weiteren – auch keine nur lesenden – Referenzen auf denselben Speicherbereich existieren.

Weitere Details zu Rust 1.63 lassen sich dem Rust-Blog entnehmen. Wie üblich können Entwicklerinnen und Entwickler, die Rust bereits installiert haben, das aktuelle Release über rustup update stable herunterladen. Für diejenigen, die noch kein Rust verwenden, ist das rustup-Tool auf der Download-Seite separat verfügbar.

Am 9. November veranstalten heise Developer, iX und dpunkt.verlag die zweite Online-Konferenz zu Rust.

(rme)