Wenn man Rust neu schreiben würde
(josephg.com)- 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,
comptimeusw. zu experimentieren - Um Supply-Chain-Risiken zu verringern, sollten sensible Funktionen wie Dateischreiben, Netzwerkzugriff, FFI und
unsafemit 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
Mutexgenannt: 25 Personen hinterließen über zwei Jahre hinweg mehr als 200 Kommentare, ohne zu einem klaren Ergebnis zu kommen
- Als Beispiel wird die Diskussion zur Verbesserung von
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
yieldbzw.awaitverwendet - 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::Outputin 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,FnOnceundFnMut, 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
unsafebei 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
- Beispiel:
- Wenn ein Third-Party-Crate die Capability
fs_writebenötigt, müsste der Aufrufer diese explizit inCargo.tomloder 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 Cratefooaber nicht für diese Capability vertraut wird - Viele Utility-Crates wie
human-sizeoderserdebenö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::writewürde einen Wert vom TypFsWriteCapabilityverlangen - Die Erzeugung von
Capability-Objekten wäre darauf beschränkt, nur im Root-Crate möglich zu sein
- Beispiel:
- Dieser Ansatz erhöht zwar den Boilerplate-Anteil, ist aber flexibler; ähnlich müssten auch
build.rs-Skripte undunsafe-Blöcke behandelt werden - Selbst wenn crates.io kompromittiert würde und in
serdeCryptolocker-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
Pinwird 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
MoveMarker Trait, das markiert, welche Typen bewegbar sind - Im heutigen Rust gibt es kein
Pin-Trait, sondernUnpinund!Unpin, und diese doppelte Verneinung mache das Konzept noch schwerer verständlich - Da
Pinnur auf Referenztypen anwendbar ist, führt es laut Kritik zu überall verstreutemBox-Wrapping- Als Beispiele werden Helper-Bibliotheken wie Tokio-Stream-Wrapper,
ouroboros,async-traitundself_cellangeführt
- Als Beispiele werden Helper-Bibliotheken wie Tokio-Stream-Wrapper,
- 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 fnerzeugt 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
xgeborrowt ist, und würde dieselben Einschränkungen anwenden wie bei geborrowten Variablen innerhalb einer Funktion
- Beispiel: eine „local borrow“-Syntax wie
- Eine solche Syntax könnte auch für self-referential Structs oder für ASTs mit Formen wie
source: Stringundast_nodes: Vec<&'Self::source str>genutzt werden - Structs mit geborrowten Feldern wären nicht bewegbar und würden daher
Movenicht implementieren; darüber hinaus wird auch einMover-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,
ifund Schleifen können als Compile-Time-Code markiert werden; nicht-comptime-Code wird in das eigentliche Programm emittiert - Die
std printcase study von Zig zeigt ein Beispiel, bei dem ein Format-String als comptime-Parameter übergeben, in einercomptime-Schleife geparst und daraus Ausgabecode erzeugt wird - Die Implementierung von Rusts
println!()ruft interne Funktionen wieformat_args_nlauf, 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>Copyimplementiert, wennT: Copygilt - Auch das Derive-Problem bei Associated Types wird als Korrekturziel genannt
- Zugehöriges Issue: derive with associated types
- Beispiel: Playground
- 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 wirdcheck_foo()auch dann ausgeführt, wennsome_varNoneist – anders als beim Short-Circuit-Verhalten eines normalenif - Beispiel: Playground
- Gewünschte Form:
- Auch die Ergonomie von Raw Pointern wird als verbesserungswürdig genannt
- Bei Referenzen kann man
myref.xschreiben, bei Pointern jedoch(*myptr).xoder(*(*myptr).p).y - Die Haltung ist, dass
unsafe-Code möglichst leicht zu lesen und zu schreiben sein sollte
- Bei Referenzen kann man
- Vorgeschlagen wird zudem, eingebaute Collection-Typen so zu ändern, dass sie im Konstruktor explizit einen
Allocatorerhalten statt eines globalen Allocators - Auch
asyncbrauche 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
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.Zum Beispiel kann man Funktionen verwenden, die
impl Traitzurückgeben, und Structs können beliebige Felder haben, aber man kann einen perimpl Traitzurückgegebenen Wert nicht in ein Struct-Feld legen. Der Grund ist, dass man dem Typ keinen Namen geben kann.if a && bfunktioniert, undif let Some(x) = xauch, aberif let Some(x) = x && bfunktioniert 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.Pinwar 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.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.
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_arrayundtypenumin Kryptografie-Bibliotheken loswerden. Trotzdem stimme ich zu, dass das Rust-Team bei neuen Features vorsichtig sein sollte.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
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.
efswist ein C++-File-System-Watcher mit 7.000 Zeilen und ohne Abhängigkeiten. Die Single-Header-Libraries aus der Spieleprogrammierung wiestb_*,cgltfsowie 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 machenhttps://github.com/SpartanJ/efsw
https://github.com/nothings/stb
https://github.com/jkuhlmann/cgltf
https://github.com/ocornut/imgui
File-Watcher sind meiner Ansicht nach alles andere als einfach, besonders wenn mehrere Plattformen unterstützt werden sollen
Teile von
cargo watchziehen 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 wieisOddoderis evenein Ö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 SeiteDie meisten Abhängigkeiten von
cargo-watchscheinen aus den drei direkten Anforderungenclap,cargo_metadataundwatchexeczu kommen.clapzieht viele plattformspezifische CLI-Elemente herein, undcargo_metadatamuss zwangsläufig viel aus demserde-Umfeld mitbringen. Beiwatchexecgibt es Verbesserungspotenzial: Es hängt voncommand-groupab, 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 alteswatchexecgebunden ist, lässt sich das manchmal über Anpassungen anCargo.lockpassend 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 seinRust 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
Spread-Operator, Generatorfunktionen,
async, Arrow Functions,leftpad, das neueDateund 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 hatRustaceans 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
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.
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.
Zum Beispiel fügt die Interaktion von
Pinund 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 ohnePin. 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.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 Autor des verlinkten Kommentars hat nach einer umfassenden Analyse der Synchronisationsprimitiven mehrerer Sprachen die Synchronisationsprimitiven von Rust wie
MutexundRwLockso neu geschrieben, dass sie je nach Betriebssystem direkt die darunterliegenden OS-Primitiven verwenden.Durch den Einsatz von Dingen wie
futexunter 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
AsyncWriteundAsyncReadnö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 wieAsyncIteratorkommen 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,tryundconstmehrfach 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
comptimewird auf mehreren Wegen gearbeitet, und kürzlich wurde ein RFC verfasst, dermacro_rulesstärken soll. DieimplfürRangeist bereits im Gang und mit inkompatiblen Änderungen über eine Edition verknüpft. Für die Kombination vonif letund 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.Deklarativere Makros, die Delegation der Logik von
-sys-Crates an gemeinsame Libraries sowiecfg(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 auchcargo-redpenzu 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 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/
Ob Rust oder etwas anderes: Wie machbar das wirklich ist, weiß man schwer, bevor man es tatsächlich versucht.