1 Punkte von GN⁺ 2024-07-22 | 1 Kommentare | Auf WhatsApp teilen

Pin

  • Der Typ Pin und das Konzept des Pinning sind grundlegende Bausteine des asynchronen Rust-Ökosystems
  • Dennoch gehört Pin zu den Elementen, die schwer zugänglich sind und leicht missverstanden werden
  • Dieser Artikel erklärt, was Pin erreicht, wie es entstanden ist und worin die aktuellen Probleme von Pin bestehen

Requirements

  • Um Referenzen in asynchronen Funktionen zu unterstützen, mussten Referenzen innerhalb von Future gespeichert werden
  • Das Problem ist, dass diese Referenzen selbstreferenziell sein können
  • Beispielcode:
    async fn foo<'a>(z: &'a mut i32) { ... }
    async fn bar(x: i32, y: i32) -> i32 {
        let mut z = x + y;
        foo(&mut z).await;
        z
    }
    
  • Der interne Zustand von Bar sieht wie folgt aus:
    enum Bar {
        Start { x: i32, y: i32 },
        FirstAwait { z: i32, foo: Foo<'?> },
        Complete,
    }
    
  • Das Ziel von Pin ist es, selbstreferenzielle Typen sicher zu handhaben

Non-solutions: move constructors and offset pointers

  • Move-Konstruktoren und Offset-Pointer funktionieren in Rust nicht
  • Move-Konstruktoren würden beim Verschieben Pointer anpassen, aber das ist in Rust nicht möglich
  • Offset-Pointer funktionieren nicht, weil sich zur Compile-Zeit nicht erkennen lässt, ob eine Referenz selbstreferenziell ist oder nicht

The “pinned typestate”

  • Ein Objekt ist nicht immer unbeweglich, sondern muss erst ab einem bestimmten Zeitpunkt unbeweglich werden
  • In Ralf Jungs Modell wechselt ein Objekt vom Zustand „owned“ in den Zustand „shared“ und dann in den Zustand „pinned“
  • Sobald es in den gepinnten Zustand übergeht, kann das Objekt nicht mehr verschoben werden

?Move

  • Vor Pin wurde eine Lösung auf Basis eines neuen Traits namens Move versucht
  • Typen, die Move nicht implementieren, wechseln beim Nehmen einer Referenz in den gepinnten Zustand
  • Move bietet jedoch keine Abwärtskompatibilität

Pin

  • Pin entwirft eine neue Art von Referenz, die ein Objekt in den gepinnten Zustand versetzt
  • Pin ist als Library-API implementiert und wahrt damit die Abwärtskompatibilität
  • Durch das zusätzliche Auto-Trait Unpin müssen die meisten Typen nicht zwischen gepinntem und normalem Zustand unterscheiden

The problems with Pin

  • Pin hat verschiedene Probleme bei der Benutzbarkeit
  • Da Pin als Library-Typ implementiert ist, fehlen viele Fähigkeiten gewöhnlicher Referenztypen
  • Zum Beispiel implementiert &mut T nicht Copy, kann aber mehrfach als Argument übergeben werden
  • Pin bietet diesen Komfort nicht
  • Beim Einsatz von Pin entsteht viel Verwirrung

In my next post…

  • Pin ermöglicht es, beliebige Referenzen in asynchronen Funktionen sicher zu kompilieren
  • Gleichzeitig erhöht Pin die Komplexität; wie sich das verbessern ließe, wird im nächsten Artikel behandelt

Zusammenfassung von GN⁺

  • Pin ist ein wichtiger Baustein des asynchronen Rust-Ökosystems
  • Die Probleme bei der Benutzbarkeit von Pin rühren daher, dass es als Library-Typ implementiert ist
  • Möglichkeiten zur Verbesserung von Pin werden im nächsten Artikel behandelt
  • Ein Projekt mit ähnlicher Funktionalität ist pin-project-lite

1 Kommentare

 
GN⁺ 2024-07-22
Hacker-News-Kommentare
  • Pin ist schwer zu verstehen, weil es in der offiziellen Dokumentation nicht klar erklärt wird

    • In der Dokumentation wird behauptet, „Pin garantiert, dass ein Objekt niemals verschoben wird“, aber das stimmt in Wirklichkeit nicht
    • Die meisten gewöhnlichen Objekte sind Unpin, daher bewirkt Pin normalerweise gar nichts
    • Die Menge der Typen T, für die Pin tatsächlich funktioniert, ist sehr speziell und wird in der Dokumentation nicht ausreichend hervorgehoben
  • Pin ist schwer, weil es für sich genommen keine Bedeutung hat

    • Bei Pin sagt weder die Sprache noch die Standardbibliothek, was Pin tun kann und was nicht
    • Stattdessen erstellt der Anbieter von InnerType zusätzliche (intern unsichere) Methoden und APIs, mit denen sich angeheftete Objekte manipulieren lassen
    • Der einzige Zweck von Pin selbst besteht darin, einen Zeiger bereitzustellen, der weniger „eingebaute Fähigkeiten“ hat
  • Dem Titel sollte „rust“ hinzugefügt werden, damit klar ist, worum es in dem Artikel geht

  • Der Begriff „value identity“ wird in der Dokumentation von Mojo nirgends definiert

    • Empfohlen wird Dave Abrahams’ Vortrag „Value Semantics: Safety, Independence, Projection, & Future of Programming“
  • Pin ist ein gutes Beispiel für einen technisch korrekten, aber schwer verständlichen Namen

    • „Drop“ hat eine vertrautere Bedeutung, „pinning“ dagegen nicht
    • Vielleicht wäre immovable!(…) besser, aber es ist schwer, einen wirklich guten Namen zu finden
    • Ein beschreibender Name wie prevent_moving!(…) und ein PreventMove-Trait könnten besser sein
  • In Rust-ähnlichen Sprachen mit Move-Konstruktoren könnte die Notwendigkeit für Pin verschwinden

    • Weil Nutzer keine Möglichkeit hätten, Objekte zu zerstören, gäbe es auch keine Möglichkeit, sie zu verschieben
  • Über &mut-Referenzen lassen sich Objekte mit mem::swap/replace verschieben, aber das wird in der Praxis selten benötigt

    • Es wäre schön, wenn es eine Möglichkeit gäbe, stattdessen Move-Referenzen zu wählen
    • swap und replace unsicher zu machen könnte das Problem lösen
  • WithoutBoats führt eine lebhafte Diskussion über asynchrone Iteratoren, poll und pin

    • Es gibt kaum Communities, die Details einer Sprache öffentlich so tiefgehend diskutieren, und das zu beobachten ist sehr interessant
  • Pinning/!Move ist nicht nur für async/await nützlich, sondern auch für viele andere Anwendungsfälle

    • Aber Rust geht damit nicht richtig um, sodass die übliche Antwort lautet: „Schreib das Programm in einer anderen Sprache neu“