1 Punkte von GN⁺ 2024-09-27 | 1 Kommentare | Auf WhatsApp teilen
  • Rust ist trotz Speichersicherheit, Performance, algebraischer Datentypen und der Vorteile von Cargo von der Einsicht geprägt, dass sich die Sprache im stabilen Kanal nur langsam weiterentwickelt und sich dadurch wie ein „noch nicht fertiggestelltes Produkt der ersten Generation“ anfühlt
  • Nachdem die Community gewachsen ist, wurde konsensbasierte Entscheidungsfindung zum Engpass, und es sammeln sich weiter Funktionen an, die zwar implementiert sind, in stable Rust aber lange nicht nutzbar sind, etwa Coroutines
  • Eine hypothetische „seph edition“ würde das bestehende Rust- und Cargo-Ökosystem beibehalten, zugleich aber breaking changes zulassen, um mit Effektsystemen, Compile-Time-Berechtigungen, Struct-Field-Borrows, comptime usw. zu experimentieren
  • Um Supply-Chain-Risiken zu verringern, sollten sensible Funktionen wie Dateischreiben, Netzwerkzugriff, FFI und unsafe mit Capabilities versehen werden, die der Aufrufer explizit erlauben muss
  • Die meisten Vorschläge sind mit bestehendem Rust nicht kompatibel, und selbst das bloße Hinzufügen von Capabilities erzeugt neue Fehlerschranken, die die SemVer-Kompatibilität brechen, sodass eine neue Edition oder ein Compiler-Fork nötig wäre

Warum Rust sich wie ein „Produkt der ersten Generation“ anfühlt

  • Rust wirkte anfangs als stark attraktive Sprache – dank algebraischer Datentypen, Speichersicherheit ohne Performanceverlust und einem modernen Paketmanager
  • Nach etwa vier Jahren Nutzung entsteht jedoch der Eindruck, dass die Sprache „immer ein bisschen unfertig“ bleibt
  • Der Kern der Kritik ist, dass neue Funktionen seltener in stable Rust gelangen und sich die Sprachentwicklung insgesamt deutlich verlangsamt hat
  • Im Rust unstable book gibt es rund 700 unstable features, ein erheblicher Teil davon betrifft Änderungen an der Standardbibliothek
  • Coroutines sind eine Funktion mit einem sieben Jahre alten RFC; im Compiler sind sie implementiert, aber Nutzer von stable Rust können sie noch immer nicht verwenden
  • Der Rust-RFC-Prozess wirkt wie ein Ort, an dem gute Ideen lange liegen bleiben
    • Als Beispiel wird die Diskussion zur Verbesserung von Mutex genannt: 25 Personen hinterließen über zwei Jahre hinweg mehr als 200 Kommentare, ohne zu einem klaren Ergebnis zu kommen

Die Idee einer hypothetischen „seph edition“

  • Vorgestellt wird ein Fork des Compilers, bei dem bestehender Rust-Code unverändert bleibt und stattdessen eine separate Rust edition namens „seph“ hinzukommt
  • In dieser Edition wären breaking changes erlaubt; solange der Compiler weiterhin das Mainline-Rust kompilieren kann, ließen sich bestehende Crates aus Cargo weiterverwenden
  • Die gewünschten Änderungen lassen sich grob in fünf Bereiche aufteilen
    • Function Traits und Effektsysteme
    • Compile-Time-Capabilities
    • Neudesign von Pin, Move und Struct-Borrows
    • Zig-artiges comptime
    • Kleine Verbesserungen an Syntax und Standardbibliothek

Function Traits und Effektsysteme

  • Rust hat Traits für Structs, aber der Vorschlag ist, auch Funktionen mit reicheren Trait-/Effekt-Eigenschaften auszustatten
  • Die Eigenschaften einer Funktion lassen sich etwa so zusammenfassen
    • ob sie panicen kann
    • ob sie eine feste Stack-Größe hat
    • ob sie bis zum Ende durchläuft oder yield bzw. await verwendet
    • bei Coroutines der Typ der Continuation
    • ob sie rein ist
    • ob sie indirekt unsafe-Code ausführt
    • ob ihre Terminierung garantiert ist
  • Wenn Parameter- und Rückgabetypen einer Funktion wie Associated Types der Funktion selbst offengelegt würden, wären Typreferenzen möglich, die in stable Rust derzeit nicht machbar sind
  • Das Beispiel zeigt Code, der einen Funktionsrückgabetyp direkt benennt und ihn wie some_iter::Output in ein Struct-Feld einfügt
  • Wenn – wie im Linux-Kernel – garantiert werden muss, dass ein bestimmter Codeblock niemals panicen darf, könnte man mit einer Markierung wie #[disallow(Panic)] die Panic-Möglichkeit inklusive rekursiver Aufrufe prüfen
  • Der Compiler behandelt bereits funktionsbezogene Traits wie Fn, FnOnce und FnMut, doch in der aktuellen Form seien sie zu dürftig
  • Als weiterführende Referenz wird auf Yoshua Wuyts’ Beitrag und Vortrag zum effect system verwiesen

Supply-Chain-Risiken mit Compile-Time-Capabilities verringern

  • Die meisten Rust-Projekte ziehen viele Third-Party-Crates ein, und selbst kleine Utility-Crates können durch bösartige Updates Supply-Chain-Risiken erzeugen
  • So wie unsafe bei der Speichersicherheit ein explizites Opt-in ist, sollten auch sensible Funktionen rund um Dateisystem, Netzwerk, FFI und Raw Pointer explizit erlaubt werden müssen
  • Vorgeschlagen wird ein Ansatz, bei dem sicherheitsrelevante Funktionen der Standardbibliothek mit Marker-Tags versehen werden
    • Beispiel: std::fs::write(path, contents) schreibt Dateien an beliebige Pfade und würde daher ein Tag wie #[cap(fs_write)] erhalten
    • Der Compiler würde den gesamten Call Tree, der diese Funktion aufruft, automatisch tainten
  • Wenn ein Third-Party-Crate die Capability fs_write benötigt, müsste der Aufrufer diese explizit in Cargo.toml oder per Annotation an der Aufrufstelle erlauben
  • Geschieht das nicht, könnte der Compiler einen Fehler ausgeben, dass foo::do_stuff() in das lokale Dateisystem schreibt, das Crate foo aber nicht für diese Capability vertraut wird
  • Viele Utility-Crates wie human-size oder serde benötigen keine speziellen Capabilities; selbst wenn ihre Autoren bösartigen Code hinzufügen würden, könnten Dateischreibvorgänge oder Netzwerkzugriffe dadurch schon beim Kompilieren blockiert werden
  • Als Alternative wird genannt, sensible APIs so zu ändern, dass sie einen separaten Capability-Parameter annehmen
    • Beispiel: std::fs::write würde einen Wert vom Typ FsWriteCapability verlangen
    • Die Erzeugung von Capability-Objekten wäre darauf beschränkt, nur im Root-Crate möglich zu sein
  • Dieser Ansatz erhöht zwar den Boilerplate-Anteil, ist aber flexibler; ähnlich müssten auch build.rs-Skripte und unsafe-Blöcke behandelt werden
  • Selbst wenn crates.io kompromittiert würde und in serde Cryptolocker-Code landete, würde dieser beim Capability-Ansatz mit Compilerfehlern stoppen, bevor er auf Entwicklerrechnern ausgeführt oder in Binärdateien eingebaut wird

Neudesign von Pin, Move und Struct-Borrows

  • Pin wird als komplizierter Hack betrachtet, der Lücken im Borrow Checker umgeht, und als eine Art Band-Aid, die aus dem Versuch entstand, Rückwärtskompatibilität zu bewahren
  • Tatsächlich gebraucht werde eher etwas wie ein Move Marker Trait, das markiert, welche Typen bewegbar sind
  • Im heutigen Rust gibt es kein Pin-Trait, sondern Unpin und !Unpin, und diese doppelte Verneinung mache das Konzept noch schwerer verständlich
  • Da Pin nur auf Referenztypen anwendbar ist, führt es laut Kritik zu überall verstreutem Box-Wrapping
    • Als Beispiele werden Helper-Bibliotheken wie Tokio-Stream-Wrapper, ouroboros, async-trait und self_cell angeführt
  • Auch Funktionen, die gepinnte Werte annehmen – etwa Future::poll(self: Pin<&mut Self>, ..) – werden komplizierter, und für Projection ist ein separates Crate nötig
  • Innerhalb von Funktionen verwaltet der Borrow Checker Variablen in Zuständen wie „owned“, „borrowed“ und „mutably borrowed“, doch diese Zustände sind für Programmierer nicht direkt sichtbar
  • Bei async fn erzeugt der Compiler ein verstecktes Struct, das den angehaltenen Zustand speichert; dabei kann ein Feld ein anderes Feld borrowen
  • Rust hat keine Syntax, um auszudrücken, dass sich ein Struct-Feld im borrowed state befindet, und auch Lifetimes zwischen Feldern lassen sich nicht direkt ausdrücken
  • Die vorgeschlagene Richtung ist daher, den Borrow Checker so zu erweitern, dass Struct-Field-Borrows direkt geschrieben werden können
    • Beispiel: eine „local borrow“-Syntax wie y: &'Self::x Vec<usize>
    • Der Compiler wüsste dann, dass x geborrowt ist, und würde dieselben Einschränkungen anwenden wie bei geborrowten Variablen innerhalb einer Funktion
  • Eine solche Syntax könnte auch für self-referential Structs oder für ASTs mit Formen wie source: String und ast_nodes: Vec<&'Self::source str> genutzt werden
  • Structs mit geborrowten Feldern wären nicht bewegbar und würden daher Move nicht implementieren; darüber hinaus wird auch ein Mover-Trait erwogen, das einen sicheren Eigen-Umzug ermöglicht

Zig-artiges comptime und die Probleme von Rust-Makros

  • Die Rust-Compilerstruktur wirkt so, als behandle sie faktisch mehrere Sprachen zugleich: Rust selbst, die Rust-Makrosprache und zusätzlich Proc Macros
  • Die Kernaussage lautet: Rust selbst ist gut, die Makrosprache dagegen nicht
  • Zigs comptime arbeitet so, dass der Compiler Teile des Codes zur Compile Time über einen kleinen Interpreter ausführt
  • Funktionen, Parameter, if und Schleifen können als Compile-Time-Code markiert werden; nicht-comptime-Code wird in das eigentliche Programm emittiert
  • Die std print case study von Zig zeigt ein Beispiel, bei dem ein Format-String als comptime-Parameter übergeben, in einer comptime-Schleife geparst und daraus Ausgabecode erzeugt wird
  • Die Implementierung von Rusts println!() ruft interne Funktionen wie format_args_nl auf, die vermutlich im Compiler hartkodiert sind
  • Daran schließt sich die Kritik an, dass offenbar nicht einmal die Rust-Compilerentwickler selbst die Rust-Makrosprache gern verwenden möchten

Kleine Korrekturen an Syntax und Standardbibliothek

  • Ein genannter Punkt ist der Wunsch, das Problem zu beheben, dass Range<T> Copy implementiert, wenn T: Copy gilt
  • Auch das Derive-Problem bei Associated Types wird als Korrekturziel genannt
  • Es gibt außerdem den Vorschlag, dass if let-Ausdrücke ein logisches AND unterstützen sollten
    • Gewünschte Form: if let Some(x) = some_var && some_expr { }
    • Der aktuelle Workaround ist Tuple-Matching mit (some_var, check_foo()), aber dabei wird check_foo() auch dann ausgeführt, wenn some_var None ist – anders als beim Short-Circuit-Verhalten eines normalen if
    • Beispiel: Playground
  • Auch die Ergonomie von Raw Pointern wird als verbesserungswürdig genannt
    • Bei Referenzen kann man myref.x schreiben, bei Pointern jedoch (*myptr).x oder (*(*myptr).p).y
    • Die Haltung ist, dass unsafe-Code möglichst leicht zu lesen und zu schreiben sein sollte
  • Vorgeschlagen wird zudem, eingebaute Collection-Typen so zu ändern, dass sie im Konstruktor explizit einen Allocator erhalten statt eines globalen Allocators
  • Auch async brauche Verbesserungen, doch das sei so umfangreich, dass es einen eigenen Beitrag erfordern würde

Kompatibilität und Umsetzbarkeit

  • Die meisten Vorschläge sind mit bestehendem Rust nicht kompatibel
  • Schon das Hinzufügen von Security-Capabilities erzeugt neue Fehlerschranken in Crates und bricht damit die SemVer-Kompatibilität, weshalb eine neue Rust-Edition nötig wäre
  • Vor einigen Jahren hätte daraus vielleicht ein RFC werden können, aber der Autor möchte vermeiden, nach langen GitHub-RFC-Diskussionen nur eine weitere unverwirklichte Idee zu produzieren
  • Am Ende kehrt der Gedanke deshalb wieder zum direkten Fork des Compilers zurück – mit der nüchternen Einschränkung, dass es dafür schlicht zu viele andere Projekte gibt

1 Kommentare

 
GN⁺ 2024-09-27
Meinungen auf Hacker News
  • Entgegen der Behauptung, der Rust-RFC-Prozess sei ein Friedhof guter Ideen, halte ich es für den richtigen Weg, dass das Rust-Core-Team es schwer macht, neue Features zur Programmiersprache hinzuzufügen.
    Man muss verhindern, dass die Oberfläche der Sprache aufgebläht, inkonsistent und unvorhersehbar wird. Swift mochte ich anfangs auch, habe es aber schließlich aufgegeben, als immer mehr Dinge dazukamen: redundante Funktionsnamen wie isMultiple(of:), Regeln zum Parsen von geschweiften Klammern für SwiftUI, Regeln zu Referenz-/Werttypen und Mutabilität, Kurzschreibweisen für Closure-Argumente usw. Gute Ideen gibt es viele; ich hoffe, Rust bleibt so schlank wie möglich.

    • Ich bin der Autor des ursprünglichen Beitrags. Wenn man Rust verwendet, stößt man oft auf Lücken, bei denen Feature X und Feature Y jeweils unterstützt werden, aber nicht zusammen genutzt werden können.
      Zum Beispiel kann man Funktionen verwenden, die impl Trait zurückgeben, und Structs können beliebige Felder haben, aber man kann einen per impl Trait zurückgegebenen Wert nicht in ein Struct-Feld legen. Der Grund ist, dass man dem Typ keinen Namen geben kann. if a && b funktioniert, und if let Some(x) = x auch, aber if let Some(x) = x && b funktioniert nicht. Solche Dinge sollten behoben werden. Gemessen an den Codezeilen des Compilers würde Rust dadurch größer, aber gemessen an der Komplexität beim Lernen und Verwenden machen solche Feature-Lücken die Sprache eher komplexer. Pin war in der Standardbibliothek kein riesiger Implementierungsaufwand, ist aber keineswegs ein schlankes Feature und verursacht eine enorme kognitive Last. Ich halte einen komplexen Borrow Checker und gut lesbaren Rust-Code für besser als einen einfachen Compiler und eine schwer zu verwendende Sprache.
    • Ein RFC zu schreiben ist einfacher, als ihn zu implementieren, und es gibt auch mehr Menschen, die RFCs schreiben können. Deshalb wird die RFC-Warteschlange bei jedem Popularitätsgrad zwangsläufig wie ein Friedhof guter Ideen aussehen.
      Selbst wenn 100 % der wartenden Ideen angenommen und irgendwann umgesetzt würden, sähe es wegen des Unterschieds zwischen Eingangs- und Ausgangsgeschwindigkeit so aus. Wer den RFC-Autoren voraus sein will, sollte keinen RFC schreiben, sondern eine produktionsreife Implementierung samt Dokumentation und Tests erstellen, die sich sauber in den Tree mergen lässt.
    • Ich stimme im Großen und Ganzen zu, aber generische assoziierte Konstanten in Traits sind in einem paradoxen Zustand: Sie sind stabilisiert, lassen sich aber nicht wie echte Konstanten verwenden.
      Man kann sie weder als const generic eines anderen Typs noch als Array-Länge nutzen. Wenn man nur einen Wert braucht, kann man einfach eine Funktion definieren, die ihn zurückgibt; derzeit sind sie also kaum nützlich. Bei richtiger Unterstützung könnte man Hilfs-Crates wie generic_array und typenum in Kryptografie-Bibliotheken loswerden. Trotzdem stimme ich zu, dass das Rust-Team bei neuen Features vorsichtig sein sollte.
    • Rust ist unter den bereits einigermaßen verbreiteten Programmiersprachen schon eine Art Wagyu; man sollte nicht noch so viel Fett hinzufügen, dass es ungenießbar wird.
    • Gute Ideen sind per Definition selten und wertvoll
  • Die Lage bei den Abhängigkeiten ist ziemlich ernst, aber es scheint kaum jemand zugeben zu wollen. Ein aktuelles Beispiel, das ich gesehen habe, ist die Crate cargo-watch
    Im Kern ist das eine einfache App, die Dateiänderungen überwacht und den Compiler erneut ausführt; die Implementierung umfasst nicht einmal 1.000 Zeilen. Wenn man die Abhängigkeiten jedoch vendort, wächst der Rust-Code auf fast 4 Millionen Zeilen in über 8.000 Dateien an. Für einen einfachen File-Watcher ist das übertrieben
    https://crates.io/crates/cargo-watch

    • Wenn man transitive Abhängigkeiten leicht macht und eine Kultur hat, in der „wenn es eine Library gibt, muss man sie auch verwenden“, dann ist das zwangsläufig das Ergebnis
      C/C++ ist unter den verbreiteten Sprachen fast der einzige Fall, in dem ein npm-artiger Paketmanager nicht populär ist. Deshalb sind die meisten Libraries in sich geschlossen oder haben nur wenige und oft optionale Abhängigkeiten. efsw ist ein C++-File-System-Watcher mit 7.000 Zeilen und ohne Abhängigkeiten. Die Single-Header-Libraries aus der Spieleprogrammierung wie stb_*, cgltf sowie Dear ImGui waren mit das Angenehmste, was ich benutzt habe. Bei einem neuen Paketmanager könnte ein Verbot transitiver Abhängigkeiten insgesamt ein Gewinn sein. Große Library-Abhängigkeiten könnte man Nutzer direkt installieren lassen, Callbacks anbieten oder sie zu Standardfunktionen machen
      https://github.com/SpartanJ/efsw
      https://github.com/nothings/stb
      https://github.com/jkuhlmann/cgltf
      https://github.com/ocornut/imgui
    • Heutzutage ist der Abhängigkeitsdschungel die übliche Art, Dinge fertigzubekommen. Das Beste, was eine Runtime tun kann, ist, das zu akzeptieren, es so schnell und sicher wie möglich zu machen und die Standardbibliothek zu erweitern, damit auch Projekte mit minimalen Abhängigkeiten unterstützt werden
      File-Watcher sind meiner Ansicht nach alles andere als einfach, besonders wenn mehrere Plattformen unterstützt werden sollen
    • Das ist ein natürlicher und nicht besonders beängstigender Vorgang. Jeder Code steht auf einem Gebirge von Abhängigkeiten, das viel mehr Funktionalität enthält, als er selbst nutzt
      Teile von cargo watch ziehen eine Wrapper-Library für die Win32-API herein; das sind automatisch generierte Bindings für Win32-Aufrufe und deshalb zwangsläufig riesig. Auch die Standardbibliotheken der meisten Sprachen umfassen Millionen Zeilen, und eine App nutzt davon nur einen Teil. C++ Boost ist ebenfalls monströs groß, aber Entwickler verwenden nur einzelne Erweiterungen daraus. Statt einer npm-artigen Hölle, in der ein Paket wie isOdd oder is even ein Ökosystem kaputtmachen kann, sind mir eher weniger, dafür größere Abhängigkeiten lieber, die von vielen Leuten gepflegt und genutzt werden; Rust liegt im Großen und Ganzen eher auf dieser Seite
    • Bei der Arbeit entfernen und schreiben wir bewusst etliche Abhängigkeiten neu, aber weder 1.000 noch 4 Millionen Zeilen wirken wie eine zutreffende Schätzung für die angemessene Codezeilenzahl dieses Projekts
      Die meisten Abhängigkeiten von cargo-watch scheinen aus den drei direkten Anforderungen clap, cargo_metadata und watchexec zu kommen. clap zieht viele plattformspezifische CLI-Elemente herein, und cargo_metadata muss zwangsläufig viel aus dem serde-Umfeld mitbringen. Bei watchexec gibt es Verbesserungspotenzial: Es hängt von command-group ab, das von derselben Organisation gepflegt wird, und dieses verlangt immer Tokio. Das größere Problem ist, dass man überzogene Abhängigkeiten anderer Crates nicht leicht beheben kann. Wenn eine Crate an ein altes watchexec gebunden ist, lässt sich das manchmal über Anpassungen an Cargo.lock passend machen, aber meistens ist es schwierig und man muss mit [patch] ausweichen. Es wäre schön, wenn man leicht einen „Stand-in“ definieren könnte, der eine bestimmte Version einer Crate ersetzt; für bestehende Paketmanager dürfte das aber ein großes Forschungsproblem sein
    • Der Großteil der Zeilen dürfte wahrscheinlich aus generierten Windows-API-Crates stammen. Die sind berüchtigt riesig
  • Rust ist keine spannende neue Sprache mehr, sondern in einer Phase auf dem Weg zu breiter Adoption. Dass die Feature-Entwicklung langsamer wird, ist natürlich und gesund
    Im Moment sind Designfehler deutlich schädlicher als ein niedriges Tempo. Rust ist nicht wegen toller Features interessant, sondern weil es zu einer neuen Klasse von Sprachen gehört, die speichersicher, ohne Garbage Collection und produktionsreif sind. Dass Rust tatsächlich an wichtigen Stellen eingeführt wird, ist interessanter, als die Sprache besser zu machen; und das wird leichter, wenn man darauf vertrauen kann, dass sie vorsichtig betrieben wird

    • JavaScript kann man in einer ähnlichen Lebensphase sehen, aber in den letzten 10 Jahren wurden hervorragende neue Features in großer Zahl hinzugefügt
      Spread-Operator, Generatorfunktionen, async, Arrow Functions, leftpad, das neue Date und so weiter – die Liste wichtiger Features endet nicht. JS ist viel älter als Rust und viel weiter verbreitet, und es gibt mehrere Produktionsimplementierungen, die neue Features alle kompatibel umsetzen müssen. Um ES5 herum gab es eine Phase der Stagnation, aber der Unterschied scheint darin zu liegen, dass sich das ECMAScript-Standardisierungskomitee zusammengerissen hat
    • Ada/SPARK gibt es schon seit langer Zeit; ich frage mich, ob das wirklich eine neue Klasse von Sprache ist. SPARK treibt Sicherheit sogar noch weiter, bis hin zu formaler Verifikation
  • Rustaceans sind so besessen davon, alles in Rust neu zu schreiben, dass ich bei einem Artikel über das Neuschreiben von Rust ernsthaft dachte, es müsse ein metasatirischer Witz sein

    • Das ist schon in prähistorischer Zeit passiert. Ursprünglich war Rust in OCaml geschrieben, wurde aber schließlich in Rust neu geschrieben
    • Ich bin der Autor des ursprünglichen Artikels. Genau diese Anspielung war im Titel beabsichtigt
    • Sie wollen, dass ihr – oder wir – alles in Rust neu schreibt. Nicht sie selbst
    • Leute aus dem Bereich Programmiersprachen mögen auch Bootstrapping. Rust in Rust zu schreiben ist nicht so abwegig
  • Es ist etwas merkwürdig, sich zuerst über langsame Entscheidungsprozesse zu beschweren und dann als Gründe für fehlende Stabilisierung Features aufzuzählen, die mit Entscheidungsfindung wenig zu tun haben.
    Zum Beispiel hängen Coroutinen fest, weil es schwer zu lösende Randfälle gibt. Im Compiler steckt keine fertige Implementierung, die man „einfach einschalten“ könnte, sondern eine unfertige Implementierung, die in vielen Fällen funktioniert, aber nicht für Stable aktiviert werden kann. Auch bei Function Traits wurde aus mehreren technischen Gründen und wegen Wechselwirkungen mit künftigen Features ausdrücklich entschieden, sie nicht in ihrer jetzigen Form zu stabilisieren. Wenn man in die Vergangenheit zurückginge, gäbe es sicher Dinge, die man anders entworfen hätte, aber das meiste hängt mit Entscheidungen aus der frühen Rust-Zeit zusammen, als Team und Ressourcen deutlich kleiner waren. Man könnte zwar meinen, dass man heute bessere Entscheidungen treffen und daher einen Bruch im Stil von Rust 2.0 schaffen sollte, aber die Katastrophe beim Übergang von Python 2 zu 3 war so groß, dass viele es für besser halten, die rauen Kanten in Kauf zu nehmen. Wenn man den wöchentlichen Rust-Newsletter liest, sieht man, dass RFC-Genehmigungen und Stabilisierungentscheidungen jede Woche abgearbeitet werden. Es gibt Fälle, die zu lange dauern, aber Probleme mit Menschen, Abstimmung und fehlender Zeit sind oft schwerer zu lösen als technische Probleme.

  • Zu diesem Artikel sollte man auch Josh Tripletts Reddit-Antwort lesen. Eines der zentralen Beispiele des Artikels, Mutex, ist schlicht falsch.
    https://old.reddit.com/r/rust/comments/1fpomvp/rewriting_rus...
    Nachtrag: Derselbe Kommentar steht auch hier:
    https://news.ycombinator.com/item?id=41655268

  • Als ich Rust lernte, war mein erster Eindruck, dass die Sprache scheinbar fast jedes vorstellbare Feature bereits enthält.
    Das heißt nicht, dass das Rust-Team nichts abgelehnt hätte, aber trotzdem wollen die Leute noch mehr Features. Manche davon sind berechtigt, andere wirken so, als müsse unbedingt ein Feature in die Sprache, das nur 2 % der Entwickler nutzen und nur 1 % der Entwickler verstehen. Eine komplexe Sprache muss nicht noch komplexer werden. Zig ist einfacher und vermutlich schneller und hat deutlich weniger Community-Drama. Ich wünschte, Zig bekäme mehr Finanzierung.

    • Es dürfte überraschen, wie viele Features von beliebigen Leuten vorgeschlagen und dann von der Rust-Community abgelehnt werden.
      Rust versucht nicht, jedes mögliche Feature aufzunehmen. Allerdings kann es so wirken, wenn man bei Dingen wie GAT oder TAIT nicht genau weiß, welches Problem sie lösen. Zig mag eine gute moderne Sprache sein, aber wenn Speichersicherheit das Ziel ist, ist es keine Option.
    • Ich bin der Autor des ursprünglichen Artikels. Meiner Ansicht nach wird Rusts Komplexitätsbudget an vielen falschen Stellen ausgegeben.
      Zum Beispiel fügt die Interaktion von Pin und Futures der Sprache eine absurde Komplexität hinzu, und ein Teil davon ist meiner Meinung nach unnötig. Ich hätte gern eine Rust-ähnliche Sprache ganz ohne Pin. Auch der Borrow Checker ließe sich vermutlich sowohl in der Syntax als auch in der Implementierung vereinfachen, auch wenn ich das noch nicht konkret durchdacht habe. Im Moment lässt sich das wohl kaum ändern, ohne die Sprache zu forken, aber Rust wird nicht die letzte Sprache mit Borrow Checker sein. Ich hoffe, dass die nächste Generation hier viel verbessern kann, ohne eine größere Sprache zu schaffen.
    • Mehr Geld könnte die falsche Art von Leuten anziehen. Wie schlecht sie sind und wie viel Schaden sie anrichten können, zeigt sich manchmal erst zu spät.
    • Ich frage mich, welches Drama in der Rust-Community gemeint ist.
      Ich sehe Drama rund um Rust, aber meist entsteht es auf der Seite, die sich gegen die Nutzung oder Einführung von Rust wehrt. Etwa der jüngste Wirbel um Rust for Linux. Innerhalb der Community scheint mir das nicht häufig vorzukommen, aber vielleicht habe ich etwas verpasst. Zig ist großartig, aber noch nicht produktionsreif.
    • Der Friedhof der in nightly begrabenen Features ist tatsächlich ziemlich groß. Auch wichtige Features wie Spezialisierung stecken dort für immer fest.
  • Der Autor des verlinkten Kommentars hat nach einer umfassenden Analyse der Synchronisationsprimitiven mehrerer Sprachen die Synchronisationsprimitiven von Rust wie Mutex und RwLock so neu geschrieben, dass sie je nach Betriebssystem direkt die darunterliegenden OS-Primitiven verwenden.
    Durch den Einsatz von Dingen wie futex unter Linux wurden sie schneller, kleiner und insgesamt besser; dabei hat er praktisch auch ein Buch über parallele Programmierung in Rust geschrieben. Es ist auch für parallele Programmierung außerhalb von Rust nützlich.
    https://www.oreilly.com/library/view/rust-atomics-and/978109...
    Er hat in diesen sieben Jahren auch nicht nur bei Coroutinen herumgespielt. Es wurden asynchrone Funktionen, Traits, die asynchrone Funktionen enthalten können, sowie mehrere Funktionen hinzugefügt, die für die Standardisierung von AsyncWrite und AsyncRead nötig sind; in nightly gibt es außerdem eine Generator-Implementierung. Diskutiert wird noch, ob man die Komplexität vollständig allgemeiner Coroutinen in Kauf nimmt oder bei Generatoren stehen bleibt. Manche Features wie AsyncIterator kommen langsam voran, aber es gibt auch viel aktive Arbeit. Es ist immer interessant, dass die eine Seite klagt, die Sprache sei zu langsam, während die andere sich beschwert, sie sei zu schnell.
    Auch bei Function-Traits und einem Effektsystem gab es zuletzt umfangreiche Design-Erkundungen; man möchte eine Lösung finden, damit Funktionen nicht für jede Kombination aus async, try und const mehrfach geschrieben werden müssen. Sandboxing für bösartige Crates ist kein Problem auf Sprachebene, sondern braucht eine Kombination aus Verifier und Runtime-Sandbox; WebAssembly-Komponenten wirken hier vielversprechender. Starkes Interesse gibt es allerdings an Compile-Time-Capabilities wie der Wahl des Allocators oder der async Runtime und Annahmen über 64-Bit-Plattformen; proc-macro-Sandboxing will man nicht zur Abwehr von Böswilligkeit, sondern für korrektes Caching.
    Selbstreferenzielle Structs sind kein Syntaxproblem, sondern ein für den Borrow-Checker sehr schwer zu behandelndes Problem. Partielle Borrows werden bei Closure-Captures bereits unterstützt; die entscheidende Frage ist, wie man stabile semantische Versionierung beibehält, wenn sie in öffentlichen APIs sichtbar werden. Ansätze wie benannte „borrow groups“ gelten als vielversprechend. Auch in Richtung comptime wird auf mehreren Wegen gearbeitet, und kürzlich wurde ein RFC verfasst, der macro_rules stärken soll. Die impl für Range ist bereits im Gang und mit inkompatiblen Änderungen über eine Edition verknüpft. Für die Kombination von if let und logischem AND gibt es ein instabiles Feature, das kurz vor der Stabilisierung steht. Es gab auch mehrere Vorschläge zur Verbesserung der Syntax für den Zugriff auf Pointer-Felder, aber ob eine weitere Vergrößerung der Sprachoberfläche mehr nützt oder schadet, ist offen. Dank der Rust-Editionen lässt sich vieles ändern, wenn das Design überzeugend genug ist. Die 700 instabilen Features sind ein echtes Problem; es braucht eine große Aufräumarbeit, um Dinge zu entfernen, deren Stabilisierung unwahrscheinlich ist.

    • So sollte die Entwicklung grundlegender Pakete auf Ebene der Standardbibliothek laufen. Die Sprachen, die wir heute verwenden, werden noch Jahrzehnte genutzt werden, und langsame, aber gute Entscheidungen heute sparen später sehr viel Zeit.
    • Es gab viele Diskussionen über Sandboxing für proc macros und Build-Skripte.
      Deklarativere Makros, die Delegation der Logik von -sys-Crates an gemeinsame Libraries sowie cfg(version) / cfg(accessible) würden den Bedarf an nutzerseitigen Implementierungen stark verringern, aber Runtime bleibt dennoch übrig. Je länger ich darüber nachdenke, desto besser wirkt cackles ACL als skalierbarer Ansatz, um Arbeit über proc macros, Build-Skripte und Runtime-Code hinweg nachzuverfolgen und die Nutzung des Dependency-Trees zu auditieren. Ich habe gehört, dass sich auch cargo-redpen zu einem Tool für Call-Audits entwickelt, aber ich stelle mir etwas auf höherer Ebene wie cackle vor.
      https://github.com/cackle-rs/cackle
    • Ich bin der Autor des ursprünglichen Beitrags. Es ist schön, die Insider-Perspektive so ausführlich zu hören.
      Ich verstehe, dass Leute sich darüber beschweren, Rust sei eine große Sprache und solle nicht noch größer werden, aber die aktuelle halbfertige async-Implementierung beizubehalten macht die Sprache nicht kleiner oder einfacher. Es macht sie nur schlechter. Auch partielle Borrows wären schon hilfreich, wenn sie zumindest innerhalb derselben Crate funktionieren würden, statt in öffentlichen APIs. In der praktischen Programmierung stößt man ständig darauf. Ich frage mich auch, warum Sandboxing für bösartige Crates auf Sprachebene unmöglich sein soll. Wenn es in einem Call-Tree keinen unsafe-Code von Drittanbietern gibt und keine Systemaufrufe erfolgen, kann eine Funktion nur mit den bereits übergebenen Argumenten, lokalen Variablen und Globals im Scope arbeiten. Wenn man diese Wand verstärken und als Sicherheitsgrenze nutzen könnte, ließe sich das Risiko in der Dependency-Supply-Chain wohl deutlich verringern.
  • Rusts Aufgabe war von Anfang an schwierig: Performance, Sicherheit und Ausdrucksstärke miteinander zu verbinden. Mit dem Rückzug von Mozilla ging der Founder Mode verloren, und da auch der ursprüngliche Kern des Teams größtenteils gegangen ist, überrascht es nicht, dass der Fortschritt langsamer wurde.
    Persönlich halte ich das für besser, als in die falsche Richtung zu laufen.

  • Wenn man Rust neu schreiben würde, würde es wohl eher darum gehen, Features zu reduzieren, statt weitere hinzuzufügen.
    So wie QBE es gegenüber LLVM macht: 70 % von Rust mit 10 % des Codes liefern. Vielleicht wäre das möglich, wenn man Makros und einige selten genutzte Features weglässt.
    https://c9x.me/compile/

    • Ich will QBE nicht kleinreden, aber QBE hatte ursprünglich das Ziel, mit 10 % des Codes 90 % der Performance zu liefern, und später wurde daraus 70 %.
      Ob Rust oder etwas anderes: Wie machbar das wirklich ist, weiß man schwer, bevor man es tatsächlich versucht.