Versuchen wir, Argumente gegen Rust vorzubringen

Ich habe kürzlich einen Artikel gelesen, in dem Rust kritisiert wird. Obwohl es viele der richtigen Dinge gab, hat es mir nicht gefallen - zu viel ist dort sehr umstritten. Insgesamt kann ich nicht empfehlen, einen Artikel zu lesen, in dem Rust überhaupt kritisiert wird. Dies ist nicht gut, da es wichtig ist, Mängel zu diskutieren, und die Verleumdung von minderwertiger und unfähiger Kritik lässt leider wirklich gute Argumente ignorieren.



Also werde ich versuchen, gegen Rust zu argumentieren .



Nicht jede Programmierung ist systematisch



Rust ist eine Systemprogrammiersprache. Es bietet eine präzise Kontrolle über die Datenzusammensetzung und das Verhalten der Codeausführung zur Laufzeit für maximale Leistung und Flexibilität. Im Gegensatz zu anderen Systemprogrammiersprachen bietet es auch Speichersicherheit - fehlerhafte Programme werden auf genau definierte Weise beendet und verhindern (möglicherweise gefährliches) undefiniertes Verhalten.



In den meisten Fällen ist jedoch keine absolute Leistung oder Kontrolle über Hardwareressourcen erforderlich. In diesen Situationen bieten moderne verwaltete Sprachen wie Kotlin oder Go durch die Verwendung eines dynamisch speicherverwalteten Garbage Collectors eine angemessene Geschwindigkeit, beneidenswerte Leistung und Speichersicherheit.



Komplexität



Programmierzeit ist teuer, und im Fall von Rust müssen Sie viel Zeit damit verbringen, die Sprache selbst zu lernen. Die Community hat hart gearbeitet, um qualitativ hochwertige Unterrichtsmaterialien zu erstellen, aber die Sprache ist sehr groß. Selbst wenn es für Sie rentabel ist, das Projekt in Rust neu zu schreiben, kann das Erlernen der Sprache selbst zu teuer sein.



Der Preis für eine verbesserte Kontrolle ist der Fluch der Wahl:



struct Foo     { bar: Bar         }
struct Foo<'a> { bar: &'a Bar     }
struct Foo<'a> { bar: &'a mut Bar }
struct Foo     { bar: Box<Bar>    }
struct Foo     { bar: Rc<Bar>     }
struct Foo     { bar: Arc<Bar>    }


In Kotlin schreiben Sie eine Klasse Foo(val bar: Bar)und lösen ein Problem. In Rust müssen Sie Entscheidungen treffen, manchmal wichtige, mit einer speziellen Syntax.



All diese Komplexität hat einen Grund: Wir wissen nicht, wie wir eine einfachere und speichersichere Low-Level-Sprache erstellen können. Aber nicht jede Aufgabe benötigt eine einfache Sprache.



Siehe auch die Präsentation Warum C ++ beim Sinken des Vaza über Wasser bleibt .



Kompilierungszeit



Die Kompilierungszeit ist ein universeller Faktor. Wenn ein Programm in einer bestimmten Sprache nur langsam ausgeführt wird, diese Sprache jedoch eine schnelle Kompilierung ermöglicht, hat der Programmierer mehr Zeit zur Optimierung, um den Programmstart zu beschleunigen!



Im Generika-Dilemma hat Rust bewusst langsame Compiler ausgewählt. Dies ist sinnvoll (die Laufzeit beschleunigt sich erheblich), aber Sie müssen bei größeren Projekten hart um angemessene Build-Zeiten kämpfen.



rustcimplementiert den wahrscheinlich fortschrittlichsten inkrementellen Kompilierungsalgorithmus in Produktionscompilern, aber es ist ein bisschen so, als würde man gegen das integrierte Kompilierungsmodell der Sprache kämpfen.



Im Gegensatz zu C ++ ist die Rust-Assembly nicht bis zum Grenzwert parallelisiert. Die Anzahl der parallelen Prozesse wird durch die Länge des kritischen Pfads im Abhängigkeitsdiagramm begrenzt. Der Unterschied macht sich bemerkbar, wenn Sie mehr als 40 Kerne kompilieren müssen.



In Rust gibt es auch keine Analoga für das Pimpl- Idiom. Um die Kiste zu ändern, müssen alle inversen Abhängigkeiten neu kompiliert (und nicht nur verknüpft) werden.



Reife



Fünf Jahre sind definitiv eine kurze Zeit, also ist Rust eine junge Sprache. Obwohl die Zukunft vielversprechend ist, ist es wahrscheinlicher, dass wir in zehn Jahren eher in C als in Rust programmieren werden (siehe Lindy-Effekt ). Wenn Sie seit Jahrzehnten Software schreiben, sollten Sie ernsthaft über die Risiken der Auswahl neuer Technologien nachdenken (obwohl sich die Wahl von Java gegenüber Cobol für Bankensoftware in den 90er Jahren im Nachhinein als die richtige Wahl herausstellte).



Es gibt nur eine vollständige Implementierung von Rust, dem rustc- Compiler . Fortgeschrittenste alternative mrustc- ImplementierungÜberspringt absichtlich viele statische Sicherheitsüberprüfungen. Derzeit unterstützt rustc nur ein produktionsfähiges Backend, LLVM. Folglich ist die Unterstützung für Prozessorarchitekturen hier enger als für C mit einer GCC-Implementierung sowie für eine Reihe proprietärer herstellerspezifischer Compiler.



Schließlich hat Rust keine offizielle Spezifikation. Die aktuelle Spezifikation ist unvollständig und dokumentiert keine geringfügigen Implementierungsdetails.



Alternativen



Neben Rust gibt es noch andere Sprachen für die Systemprogrammierung, darunter C, C ++ und Ada.



Modernes C ++ bietet Tools und Richtlinien zur Verbesserung der Sicherheit. Es gibt sogar einen Vorschlag zur Lebensdauer von Objekten im Rust-Stil! Im Gegensatz zu Rust garantiert die Verwendung dieser Tools nicht, dass keine Probleme mit der Speichersicherheit auftreten. Wenn Sie jedoch bereits eine große Menge an C ++ - Code unterstützen, ist es sinnvoll, dies zu überprüfen. Vielleicht hilft das Befolgen der Empfehlungen und die Verwendung von Desinfektionsmitteln bei der Lösung von Sicherheitsproblemen. Dies ist schwierig, aber eindeutig einfacher als das Umschreiben des gesamten Codes in einer anderen Sprache!



Wenn Sie C verwenden, können Sie formale Methoden anwenden, um dies zu beweisenkein undefiniertes Verhalten oder einfach alles gründlich testen .



Ada ist speichersicher, es sei denn, Sie verwenden dynamischen Speicher (niemals aufrufen free).



Rust ist eine interessante Sprache für die Kosten der Sicherheit, aber bei weitem nicht die einzige!



Werkzeugset



Rusts Werkzeuge sind nicht perfekt. Das grundlegende Toolkit, der Compiler und das Build-System ( Fracht ) werden häufig als Klassenbester bezeichnet.



Zum Beispiel fehlen einige Tools zur Laufzeit (hauptsächlich für die Erstellung von Heap-Profilen) - es ist schwierig, an die Laufzeit zu denken, wenn das Tool einfach nicht vorhanden ist! Darüber hinaus bleibt die IDE-Unterstützung weit hinter dem Zuverlässigkeitsniveau von Java zurück. Ein automatisiertes komplexes Refactoring eines Programms mit Millionen von Zeilen ist in Rust einfach nicht möglich.



Integration



Was auch immer Rust verspricht, die heutige Welt der Systemprogrammierung spricht C und C ++. Rust versucht nicht absichtlich, diese Sprachen zu imitieren - es werden keine Klassen im C ++ - oder C ABI-Stil verwendet.



Dies bedeutet, dass Brücken zwischen den Welten gebaut werden müssen. Die Integration ist nicht nahtlos, unsicher, nicht immer kostengünstig und erfordert eine Synchronisation zwischen den Sprachen. Während die Integration an bestimmten Stellen funktioniert und die Werkzeuge konvergieren, gibt es aufgrund der Gesamtkomplexität gelegentlich Hindernisse auf dem Weg.



Ein spezielles Problem ist, dass das überbewusste Weltbild von Cargo (ideal für reine Rust-Projekte) die Integration in größere Build-Systeme erschweren kann.



Performance



"Verwenden von LLVM" ist keine einheitliche Lösung für alle Leistungsprobleme. Obwohl ich keine Benchmarks kenne, die die Leistung von C ++ und Rust im Allgemeinen vergleichen, ist es nicht schwer, sich Aufgaben vorzustellen, bei denen Rust C ++ unterlegen ist.



Das wahrscheinlich größte Problem ist, dass die Verschiebungssemantik von Rust wertbasiert ist ( memcpyauf der Ebene des Maschinencodes). Andererseits verwendet die C ++ - Semantik spezielle Referenzen, aus denen Daten entnommen werden können (Zeiger auf der Ebene des Maschinencodes). Theoretisch sollte der Compiler die Kopienkette sehen , in der Praxis ist dies häufig nicht der Fall: # 57077 . Ein verwandtes Problem ist die fehlende Zuweisung neuer Daten. Rust muss manchmal Bytes zum / vom Stapel kopieren, während C ++ ein vorhandenes Objekt erstellen kann.



Witzigerweise ist der Standard-Rust-ABI (der die Stabilität für die Effizienz opferte) manchmal schlechter als C: # 26494 .



Während Rust-Code theoretisch aufgrund der wesentlich umfangreicheren Informationen zu Aliasen effizienter sein sollte, führt das Aktivieren von Alias-bezogenen Optimierungen zu LLVM-Fehlern und einer falschen Kompilierung: # 54878 .



Aber auch dies sind seltene Beispiele, manchmal geht der Vergleich in die andere Richtung. Zum Beispiel in BoxRust keine Performance - Probleme, die haben in std::unique_ptr.



Ein potenziell größeres Problem ist, dass Rust mit seinen generischen Definitionen weniger aussagekräftig ist als C ++. Also ein paar formelhafte Tricks C ++ für hohe Leistung kann nicht in Rust mit guter Syntax ausgedrückt werden.



Unsicherer Wert



Vielleicht ist die Idee unsafefür Rust noch wichtiger als Eigentum und Ausleihen. Durch die Aufteilung aller gefährlichen Vorgänge in Blöcke unsafeund Funktionen und das Bestehen auf einer übergeordneten sicheren Schnittstelle kann ein System erstellt werden, das gleichzeitig:



  1. zuverlässig (ungeprüfter unsafeCode kann kein undefiniertes Verhalten verursachen),

  2. modular (verschiedene unsichere Blöcke können separat getestet werden).


Es ist ziemlich offensichtlich, dass dies der Fall ist: Fuzzing Rust-Code findet Panik, aber keine Pufferüberläufe.



Aber die theoretischen Aussichten sind nicht so gut.



Erstens gibt es keine Definition eines Rust-Speichermodells, so dass es unmöglich ist, formal zu prüfen, ob ein bestimmter unsicherer Block gültig ist oder nicht. Es gibt eine inoffizielle Definition von "Dingen, auf die sich rustc verlassen kann oder kann", und an einem Laufzeitprüfer wird derzeit gearbeitet , aber das tatsächliche Modell ist nicht klar. Daher gibt es möglicherweise irgendwo einen unsicheren Code, der heute einwandfrei funktioniert, aber morgen für ungültig erklärt wird und in einem Jahr eine neue Compileroptimierung einführt.



ZweitensEs wird angenommen, dass unsichere Blöcke nicht wirklich modular sind. Stark genug unsichere Blöcke können die Sprache tatsächlich erweitern. Diese beiden Erweiterungen machen isoliert nichts falsch, führen jedoch bei gleichzeitiger Verwendung zu undefiniertem Verhalten: siehe Beobachtbare Äquivalenz und Unsicherer Code.



Schließlich gibt es offensichtliche Fehler im Compiler .



Hier sind einige Themen, die ich bewusst weggelassen habe:



  • Wirtschaft ("schwerer zu findende Rust-Programmierer") - Ich denke, der Abschnitt "Reife" spiegelt die Essenz dieser Frage wider, die nicht auf das Henne-Ei-Problem beschränkt ist.

  • Abhängigkeiten ("stdlib ist überall zu klein / zu viele Abhängigkeiten") - Angesichts der guten Fracht und der relevanten Teile der Sprache sehe ich dies persönlich nicht als Problem.

  • Dynamische Verknüpfung ("Rust sollte einen stabilen ABI haben") - Ich denke nicht, dass dies ein starkes Argument ist. Die Monomorphisierung ist grundsätzlich nicht mit der dynamischen Verknüpfung kompatibel. Wenn dies wirklich erforderlich ist, gibt es einen C-ABI. Ich denke wirklich, dass die Dinge hier verbessert werden können, aber es ist unwahrscheinlich, dass wir über spezifische Änderungen in Rust sprechen .


Diskussionsthema in / r / rust .



All Articles