- Scylla schlägt einen Weg vor, ursprüngliches C schrittweise umzustrukturieren, damit es direkt in sicheres Rust kompiliert werden kann, statt bestehendes C erst nach unsafe Rust zu übertragen und anschließend zu reparieren
- Ziel ist eine applicative C-Teilmenge mit Datenverarbeitung, Pointer-Arithmetik, strukturiertem Kontrollfluss und portablem Code; ausgeschlossen sind Code mit Abhängigkeit von
goto, Integer-Pointer-Casts, Pointer-Tricks, Bitfeldern und untagged unions - Die Transformation besteht aus zwei Schritten: Zuerst wird der Clang-AST zu Mini-C abgesenkt, anschließend nach sicherem Rust übertragen; dabei werden subtile C-Verhaltensweisen wie Integer-Promotions, implizite Konvertierungen, Zuweisungsausdrücke sowie Prä-/Post-Inkrement explizit gemacht
- Die schwierigste Hürde, die Pointer-Arithmetik, wird in Aufteilungen auf Basis von Rust-Slices und
split_at_mut/split_atumgewandelt; außerdem werden Mutabilitätsinferenz, Trait-Ableitung sowie die Wahl zwischenBoxund Borrow mitbehandelt - Bewertet wurden Teile von Windows SymCrypt, HACL★, der zentrale Kompressionsalgorithmus von bzip2, der EverParse-CBOR-Parser/-Serializer sowie Teile von Microsoft FrodoKEM; dabei wurden während der Übersetzung auch undefined behavior in bzip2 und FrodoKEM gefunden
Der von Scylla gewählte Migrationspfad von C zu Rust
- Die Motivation, bestehenden C-Code nach Rust zu übertragen, geht von Problemen der Speichersicherheit aus
- Studien von Google und Microsoft schätzen, dass 70 % der Sicherheitslücken mit fehlerhafter Speicherbehandlung zusammenhängen
- Unternehmen und Regierungen empfehlen für sicherheitskritische Systeme den Einsatz speichersicherer Sprachen wie Rust
- Rust bietet klare Vorteile für neuen Code, aber es ist schwierig, bereits getesteten und debuggten industriellen C-Code vollständig neu zu schreiben
- Bestehende automatische Werkzeuge für C→Rust geben zur Unterstützung des gesamten C-Sprachumfangs meist unsafe Rust aus
- Sie erlauben C-typische Idiome wie unchecked pointer oder Rust-Transmutation
- Dadurch entfällt die statische Garantie auf Speichersicherheit, und der Zweck einer sicheren Sprache wird geschwächt
- Ein typischer Workflow nutzt die Ausgabe in unsafe Rust als Ausgangspunkt und refaktoriert sie anschließend schrittweise zu sicherem Rust
- Es wurden statische Analysen vorgeschlagen, die raw pointer in sichere Rust-Borrows umwandeln oder Rust-Abstraktionen aus Low-Level-Darstellungen rekonstruieren
- Refactoring-Werkzeuge sind verstreut, und die Unterstützung für
c2rust refactorwurde 2022 eingestellt
- Statt erzeugtes unsafe Rust zu reparieren, verfolgt Scylla den Ansatz, den ursprünglichen C-Code selbst schrittweise umzustrukturieren, damit er in sicheres Rust kompiliert werden kann
Unterstützte C-Teilmenge und ausgeschlossene Muster
- Ziel von Scylla ist eine vorhersagbare Transformation und die Erzeugung von Rust-Code, der dem ursprünglichen C nahe bleibt
- Unterstützt wird eine applicative C-Teilmenge
- Code zum Manipulieren und Verarbeiten von Daten
- Code mit Pointer-Arithmetik
- Code mit strukturiertem Kontrollfluss
- Portabler Code
- Codebasen, die auf folgenden Mustern beruhen, werden nicht unterstützt
goto- Nutzung von Objektrepräsentationen über Integer-Pointer-Casts
- Pointer-Tricks
- Bitfelder
- untagged unions
- Entwickler können auf den C-Quelltext gezielte Umschreibungen und Annotationen anwenden, damit Scylla ihn versteht
- Aliasing-Muster, die nicht zum Rust-Borrow-Checker passen, können umgeschrieben werden
- Es kann Scylla mitgeteilt werden, dass tagged unions in höherstufige ADTs übersetzt werden sollen
Mini-C: eine Zwischensprache zur Reduktion von C-Mehrdeutigkeiten
- Scylla startet beim AST des Clang-Frontends und übersetzt ihn zunächst in eine Sprache namens Mini-C
- Mini-C behandelt wie C Verzweigungen, Schleifen, Pointer, Dereferenzierung und Adressnahme, hat aber eine „no-surprises“-Semantik
- Alle Integer haben feste Breite
- C-Integer-Promotion und Integer-Konvertierung werden als explizite Casts dargestellt
- Untyped pointer wie
void *sind nicht erlaubt
- Anders als C ist Mini-C eine Ausdruckssprache
- Zuweisungen liefern keinen Wert zurück
- C-Konstrukte wie
e1 = e2 = e3oderp[i++]werden in Mini-C desugart - Testausdrücke in Schleifen und Bedingungen müssen vom Typ
boolsein, nicht wie in Cint
- Beim Überführen des typisierten Clang-AST in einen typisierten Mini-C-AST werden implizite C-Verhaltensweisen explizit gemacht
- Bedingungen werden auf
boolangepasst - Array-Indizes werden auf
size_tangepasst - Implizite Konvertierungen bei Funktionsargumenten und auf der rechten Seite von Zuweisungen werden in explizite Casts umgewandelt
- Bei arithmetischen Operationen werden die usual arithmetic conversions des C-Standards berücksichtigt
- Bedingungen werden auf
- Die Transformation setzt voraus, dass der C-Code portabel ist und nicht vom C-Datenmodell abhängt
- Es wird also beispielsweise nicht erwartet, dass sich Verhalten danach richtet, ob
long4 oder 8 Byte groß ist - Die Implementierung erkennt zur Configure-Zeit das Datenmodell der Zielarchitektur und wandelt
unsigned intin Typen fester Breite wieuint32_tum
- Es wird also beispielsweise nicht erwartet, dass sich Verhalten danach richtet, ob
ADTs und Tupel-Synthese
- Mini-C bietet höherstufige ADTs, Tupel und Pattern Matching, die C so nicht hat
- Tagged unions werden über Annotationen in ADTs übersetzt
- Die Zielform ist eine Struktur wie
{ int tag; union { t0 case0; ...; tn caseN }} - Es wird angenommen, dass die Tag-Werte von 0 bis N reichen und mit der Reihenfolge der Union-Cases übereinstimmen
- Die Zielform ist eine Struktur wie
- Scylla wandelt annotierte tagged-union-Typen in Variant-Typen um
- Beim Erzeugen eines Werts wird geprüft, ob
.tag = iund.casej = ezusammenpassen - Falls ja, wird der entsprechende Constructor-Wert erzeugt
- Stimmen Payload und Tag nicht überein, wird nicht nach Mini-C übersetzt
- Beim Erzeugen eines Werts wird geprüft, ob
- Zugriff auf Felder einer tagged union ist nur sicher, wenn der aktuelle Tag-Zustand bekannt ist
- Muster wie
if (x.tag == i) { ... x.casei }oderswitchwerden erkannt - Sie werden in eine Form wie
match x with | Ci v -> ...umgewandelt - Zugriffe auf andere Union-Cases gelten als ungültig und führen zu einem Übersetzungsfehler
- Muster wie
- Auch Tupel können per Annotation synthetisiert werden
- Eine Struktur mit
nFeldern wird in einn-äres Tupel umgewandelt - Feldzugriffe werden zu Tupel-Feldzugriffen
- Tupel sind strukturell typisiert und profitieren dadurch von Mutabilitäts-Polymorphie
- Eine Struktur mit
Übersetzung von Mini-C nach sicherem Rust
- Mini-C bietet eine Darstellung vollständiger C-Programme mit vollständigen Typannotation und wird anschließend in sicheres Rust übersetzt
- Es gibt drei zentrale Schwierigkeiten
- Beseitigung der Pointer-Arithmetik von C
- Explizites Machen von Mutabilität und Aliasing
- Automatische Bereitstellung rusttypischer Strukturen wie Traits
- Die Übersetzung von Pointer-Typen ist wegen der unterschiedlichen Pointer-Darstellung in Rust komplex
- Rust unterscheidet zwischen
Box<T>und&T - Pointer auf ein einzelnes Element und auf mehrere Elemente werden ebenfalls unterschieden, etwa als
&Tund&[T] - Rust-Arrays sind Werte und zerfallen nicht automatisch wie in C zu Pointern
- Rust unterscheidet zwischen
- Die Grundstrategie besteht darin, alle Pointer-Typen aus C in Rust zunächst als Slice-Borrow
&[T]zu kompilieren- Sowohl Stack- als auch Heap-Pointer werden zunächst zu Slice-Borrows
- Auch Pointer auf einzelne oder mehrere Elemente werden zunächst auf Slice-Borrows abgebildet
- Mutabilität wird in einem separaten Schritt automatisch inferiert
- Mit Heuristiken und manuellen Annotationen übersetzt Scylla einige Pointer in
Box<T>- Funktionen wie
T *create(), die offenbar frische Allokationen zurückgeben und keine globalen Referenzen haben, können zufn create() -> Box<T>werden - Diese Analyse wird rekursiv bis in Struct- und Variant-Definitionen hinein per Fixpunktverfahren angewandt
- Structs, die Borrows enthalten, erhalten Lifetime-Parameter
- Funktionen wie
Einschränkungen bei der Umwandlung von Box, Slice und Array
- Da Rust zwischen Arrays, Slice-Borrows und
Boxexplizite Konvertierungen verlangt, arbeitet auch die Rust-Übersetzung von Scylla typengetrieben - Die Transformationsregeln fügen Coercions ein, um Arrays oder boxed slices in Slice-Borrows umzuwandeln
- Arrays werden in Formen wie
&x[..]zu Slice-Borrows - boxed slices können in Borrows umgewandelt werden
- Arrays werden in Formen wie
- Auch die Umkehrung ist möglich
- Slices oder Arrays können auf Heap-Allokationen angehoben und in
Box<[T]>umgewandelt werden
- Slices oder Arrays können auf Heap-Allokationen angehoben und in
- Diese Umkehrung kann Unterschiede in der Kopiersemantik erzeugen
- In C können Arrays und Pointer auf denselben Speicher zeigen
- In Rust kann
Box::new(x)eine Kopie vonxerzeugen - Bei Arrays primitiver Typen wie Integer-Arrays gibt es keine Möglichkeit, sich vom
Copy-Trait abzumelden, sodass Rust stillschweigend kopieren kann
- Wenn solche Transformationen auftreten, entfernt Scylla die ursprüngliche Variable aus der Umgebung und verbietet ihre weitere Verwendung
- Falls das ursprüngliche C-Programm die Variable weiterverwendet, entsteht ein Übersetzungsfehler
- Entwickler müssen den C-Quellcode vor der Transformation anpassen, um die Absicht klarer zu machen
Pointer-Arithmetik in Rust-Slice-Aufteilungen umwandeln
- C-Programme greifen auf Arrays oft nicht nur über einen einzelnen Basis-Pointer zu, sondern teilen Arrays in Chunks auf oder iterieren mit einem aktuellen Positions-Pointer
- Rust erlaubt keine beliebige Pointer-Arithmetik und bietet stattdessen das Teilen von Slices mit
split_at_mutodersplit_atsplit_at_mutist ein Primitive, das den Besitz des ursprünglichen Slices aufgibt und zwei Sub-Slices liefert- So bleibt die Rust-Invariante erhalten, dass veränderbare Daten einen eindeutigen Owner haben müssen
- Um C-Pointer-Arithmetik an dieses Rust-Modell anzupassen, führt Scylla einen Split-Tree ein
- Jeder C-Pointer wird auf genau einen Split-Tree abgebildet
- Ein Split-Tree verändert sich abhängig vom Kontrollfluss
- Er verfolgt, welcher Rust-Slice-Zugriff an einer bestimmten Programmstelle einen C-Pointer-Zugriff ersetzen muss
- Da C-Pointer keine Längeninformation tragen, nimmt Scylla an, dass sich Chunks nicht überlappen
- Falls Überlappung beabsichtigt ist, wird die Rust-Typprüfung fehlschlagen, und Entwickler müssen den C-Code umschreiben
- Damit die Übersetzung vorhersagbar bleibt, wird Backtracking vermieden und nur vorwärts gearbeitet
- Im Beispiel wird ein 32-Byte-Array
abcdin vier 8-Byte-Limb-Bereiche aufgeteilt- In C wird Pointer-Arithmetik in einer nicht strikt links-nach-rechts verlaufenden Reihenfolge genutzt, etwa
abcd + 0,abcd + 16,abcd + 8,abcd + 24 - Die Rust-Übersetzung behält die Historie der
split_at_mut-Aufrufe im Split-Tree bei, um den korrekten Sub-Slice zu finden
- In C wird Pointer-Arithmetik in einer nicht strikt links-nach-rechts verlaufenden Reihenfolge genutzt, etwa
Evaluierte Ziele und gefundenes undefined behavior
- Die Scylla-Implementierung verwendet Clang, nimmt bestehenden C-Code als Eingabe und erzeugt sicheres Rust
- Zur Evaluierung gehörten Teile mehrerer bestehender C-Projekte
- Teile von Windows SymCrypt
- Teile der Kryptobibliothek HACL★
- Kernteile des Kompressionsalgorithmus von bzip2
- der binäre CBOR-Parser und Serializer der EverParse-Bibliothek
- Teile der Implementierung von Microsofts Post-Quantum-Kryptoprimitiv FrodoKEM
- Diese Fälle zeigen, dass Scyllas applicative C-Teilmenge mehrere sicherheitskritische Anwendungen abdecken kann
- Während der Übersetzung wurde auch undefined behavior im ursprünglichen C-Code von bzip2 und FrodoKEM identifiziert und gemeldet
1 Kommentare
Kommentare auf Hacker News
Wichtig ist, dass sich diese Arbeit auf „bereits formal verifizierte C-Codebases“ bezieht.
Gewöhnlicher System-C-Code ist nicht formal verifiziert, das ist also eine ziemlich andere Geschichte.
Zum Beispiel könnte ein Pointer auf ein C-Array auf dem Stack in Rust als Pointer übersetzt werden, der mit
Box<[u8]>eine neue Heap-Kopie besitzt. Wenn der ursprüngliche Code sich darauf verlassen hat, dass der Pointer tatsächlich auf das Array zeigt, kann der übersetzte Code stillschweigend falsch funktionieren.Die Funktion zur automatischen Übersetzung einer speichersicheren C++-Teilmenge in meinem Projekt scpptool hätte das wohl so behandelt, dass Arrays in Ersatztypen und Iteratoren überführt werden, um die ursprüngliche Semantik zu erhalten.
Das OP-Projekt behandelt vielleicht nur C, das sich leicht in sicheres Rust umwandeln lässt; angesichts der Schwierigkeit des Problems ist die Leistung aber respektabel und ein gewisser Nutzen ist erkennbar.
Zunächst einmal haben sie kein echtes C übersetzt, sondern Code, der in F* geschrieben wurde, so verändert, dass der C-Compiler Rust ausgibt. Sie hatten es nicht mit komplexem realem C-Code zu tun, sondern allenfalls mit einem eingeschränkten Mini-C, wie es ein Toy-Compiler ausgeben könnte.
Auch der Originaltext sagt: Wenn das ursprüngliche C-Programm stärker von
xabhängt, gibt die Übersetzung einen Fehler aus und verlangt vom Programmierer, den Quellcode zu ändern. Das heißt, man hofft darauf, dass C bereits in einem Stil geschrieben ist, der den Rust-Borrow-Checker zufriedenstellt.Das wirkt wie die akademische Formulierung: „Abbildung 4 präsentiert schöne Regeln, aber die tatsächliche Implementierung stützt sich auf zahlreiche Tricks.“
Noch schlimmer: Statisch unterscheidbare Überlappungen führen zu einem Compile-Fehler, andernfalls kann der Rust-Code zur Laufzeit panicken. Es ist seltsam, ein formal verifiziertes C-Programm in ein Rust-Programm zu verwandeln, das „jetzt auch abstürzen kann“.
HACL* als bestehende formal verifizierte C-Codebase zu bezeichnen, ist ebenfalls ungenau. HACL* wird zwar nach C kompiliert, ist aber keine C-Bibliothek, sondern in einer völlig anderen Sprache geschrieben.
Ein ehrlicher Titel hätte eher lauten müssen: „Eine Teilmenge von F* nach teilweise sicherem Rust kompilieren, teilweise formalisiert“.
2002 veröffentlichten Forschende ein Paper zu Cyclone, einem sicheren Dialekt von C, und fanden beim manuellen Portieren von C-Code nach Cyclone Sicherheitsfehler im bestehenden C-Code.
Solche manuellen oder automatischen C-Transformationen haben das Potenzial, nicht nur die Einführung sichererer Sprachen zu fördern, sondern auch bestehende Bugs offenzulegen.
[1] https://www.researchgate.net/profile/James-Cheney-2/publicat...
Mehrere Ideen aus Cyclone sind in Rust eingeflossen, und mit Aufwand lässt sich der Code zum Laufen bringen, aber auf modernen 64-Bit-Plattformen baut er nicht ohne Weiteres.
http://cyclone.thelanguage.org
Ich habe C2Rust als ersten Schritt verwendet, um einige Projekte, darunter C-Projekte, nach Rust zu portieren, und bin zu ein paar Schlussfolgerungen gekommen:
unsafeenthalten ist, dank der starken Einschränkungen von Rust, etwa Bounds Checks und strikten Signaturen.Trotzdem sind Tools fürs Portieren unverzichtbar, und je besser die Tools werden, desto reibungsloser wird der Prozess.
Man kann Speicher als Array darstellen und Pointer als Indizes in dieses Array behandeln. So lassen sich C-Verhalten wie ungeprüfte Pointer-Arithmetik oder Unions ausdrücken, ohne gegen den Borrow-Checker zu kämpfen, und die Semantik bleibt erhalten. Bei C→Java werden ähnliche Techniken schon lange verwendet.
Der Wert einer solchen Transformation ist natürlich fraglich. Im Grunde ähnelt es dem Kompilieren von C nach wasm, ist aber langsamer; und selbst wenn der resultierende Code technisch „sicher“ ist, bleiben Probleme bestehen: Buffer Overflows können falsche Zustände erzeugen, oder Dangling Pointer können Zugriff auf Daten in Kontexten ermöglichen, in denen das nicht erlaubt sein sollte.
Ich bin der Autor. Es dürfte hilfreich sein, ein paar Punkte aus mehreren Threads zusammenzufassen:
unsafe Rustzurückfallen.Was ich mich wirklich frage, ist, warum man das so machen sollte.
Wenn es eine Technik ist, mit der sich industrielle Anwendungen wirklich von C nach Rust umwandeln lassen, müsste man bestehende C-Anwendungen doch auch leichter bulletproof machen können. Man müsste nur Analysen entwickeln, die in bestehende Werkzeuge wie statische Analysatoren oder Testgeneratoren einfließen.
Ebenso könnte man sichere Wrapper erzeugen und neuen Code in Rust neben verifiziertem C schreiben lassen. Der neue Code profitiert von Rust, der bestehende Code wird als sicher bestätigt, und die Schnittstellen werden ebenfalls sicherer.
Ein vollständiger Übersetzer mag ideal sein, weil es langfristig wünschenswert ist, wenn eine Codebasis nur eine Sprache verwendet. Der größte Bedarf bleibt aber nach wie vor eine Ein-Klick-Sicherheitsverbesserung mit wenigen False Positives für bestehendes C/C++. Auch automatisches Korrigieren schlechter Strukturen innerhalb von C, wie bei Googles Compiler-Tools oder ForAllSecures Mayhem, könnte möglich sein.
Das kann daran liegen, dass sie von undefiniertem oder nicht spezifiziertem Verhalten abhängen, oder daran, dass geeignete Sicherheitsprüfungen den zulässigen Eingabebereich so stark verkleinern würden, dass das Programm nutzlos würde.
Eine Übersetzung in eine sichere Sprache ist in solchen Fällen objektiv besser, weil sie die Ausdruckskraft der Eingaben erhält und zugleich zur Laufzeit korrektes Verhalten statisch garantieren kann.
„In der Praxis bewährtes C“ gibt es, wie zahlreiche schwere Sicherheitslücken zeigen, kaum wirklich. Was es tatsächlich gibt, ist C, das oft genug ziemlich gut funktioniert, sodass es nützlich wirkt.
Bei altem Code nimmt man mit etwas Glück an, dass er sicher ist; bewiesen ist er nicht. „Beweis“ hat, besonders im Kontext eines solchen Papers, eine konkrete Bedeutung, und die überwältigende Mehrheit von C-Code ist nach strengen mathematischen Maßstäben nicht bewiesen. Das Rust-Typsystem hingegen ist mathematisch als korrekt bewiesen.
Ein vollständiger Übersetzer hängt davon ab, worauf man verzichten kann. Wenn man Performance, Eingabebereich, Ausgabebereich, Lesbarkeit des Codes usw. aufgeben darf, ist es bis zu einem gewissen Grad möglich; problematisch wird es, sobald man in all diesen Dimensionen einen sounden und vollständigen Übersetzer haben will.
Wenn man naiv nach Rust übersetzt, bekommt man dann nicht eine Mischung aus sicheren und
unsafeTeilen? Dann müsste man manuell nur die Sicherheit derunsafe-Bereiche prüfen. Das wäre ähnlich wie beim Schreiben von Rust von Grund auf.Wenn 90 % des Ergebnisses nicht
unsafesind, könnte das ein ziemlich großer Gewinn sein.unsafe Rustumgewandelt.Von OpenJPEG war bekannt, dass es bei einem bestimmten Testfall einen Segfault auslöst; als man diesen Test auf der Rust-Version laufen ließ, trat der Segfault an der entsprechenden Stelle im Rust-Code auf. Zumindest war es also kompatibel.
Dieser Ansatz ist aber eine Sackgasse. Um voranzukommen, muss der Übersetzer gängige C-Idiome erkennen und in eine natürliche Form der Zielsprache anheben. Wenn man nach Rust „kompiliert“, entsteht grauenhaftes Rust voller Aufrufe unsicherer, C-artiger Pointer-Manipulationsfunktionen.
Das größte Problem beim Anheben hängt meist mit Pointern zusammen. Das vielversprechendste Ergebnis dieses Papers ist, dass es einen Weg gefunden hat, C-Pointer-Arithmetik in Rust-Slices umzuwandeln. Slices können das meiste leisten, was C-Pointer-Arithmetik tut, und nun hat jemand diese Übersetzung automatisiert. Pointer-Arithmetik, die sich nicht übersetzen lässt, sollte man sehr misstrauisch betrachten.
Es ist hilfreich, sich vorzustellen, dass ein Raw Pointer auf ein Array in C implizit eine Länge hat. Diese Länge erscheint nicht im C-Quelltext, existiert aber irgendwo als Funktion des Programmzustands. Sie kann eine Konstante sein, die Größe einer
malloc-Anforderung oder ein Funktionsparameter. Für Wartungsprogrammierer ist es meist nicht allzu schwer, die Array-Länge zu finden.Das könnte ein Problem sein, das gut zu LLMs passt: Man fragt „Sieh dir diesen Code an und finde heraus, wie lang das Array
fooist“, und lässt dann einen nicht-LLM-basierten Übersetzer die Rust-Umwandlung daran ausrichten. Wenn das LLM falschliegt, erzeugt Rust einen Indexfehler oder ein zu großes Array, aber es wird nicht unsicher. Die C-Idiome für Arraygrößeninformationen sind ausreichend formalisiert, sodass man sie meist korrekt erkennen können sollte. Insbesondere können LLMs auch Kommentare lesen.unsafeRust-Code erzeugen, weil überall Raw Pointer statt Referenzen verwendet würden.C-Code wird nicht mit Rusts Alias-Modell und den Einschränkungen des Borrow Checkers im Hinterkopf geschrieben, daher ist es schwierig, ihn in Referenzen zu übersetzen.
Es geht nur darum, eine sehr kleine C-Teilmenge zu kompilieren. In der Praxis könnte sie so klein sein, dass sie nutzlos ist.
An diesen Ansatz habe ich geringe Erwartungen. Er wird zwangsläufig an die Grenzen dessen stoßen, was mit statischer Analyse von C-Code möglich ist. Außerdem wird das Problem durch die Wahl von Rust als Ziel unnötig schwierig. Rusts Ownership-Modell unterscheidet sich zu stark davon, wie reale C-Programme funktionieren.
Zum Beispiel haben Puffer in C natürlich eine Länge, aber in C ist die Länge nicht explizit an den Pointer gebunden. Daher muss der Übersetzer ableiten, wie das C-Programm die Länge verfolgt, und das in Slices umwandeln. Selbst wenn die Länge eine explizite Variable ist, ist das nicht einfach; noch kniffliger wird es, wenn sie berechnet wird oder als „Pointer hinter das Ende“ dargestellt ist.
Auch C-Muster wie
bool should_free_this_pointerlassen sich in Rust alsOwned/Borrowed-Enum übertragen, aber man muss ableiten, welche Allokation mit welchem Boolean verbunden ist und wo der tatsächlich sichere Bereich der geliehenen Variante liegt.Trotzdem stimme ich zu, dass es schwierig sein wird, aus beliebigem C idiomatisches Rust zu machen. Es wäre sozusagen nur „ungefähr richtig“.
Ich frage mich, wie sich das mit der C-Übersetzungsfunktion von Zig vergleicht.
Zig scheint hervorragend darin zu sein, eine gemischte Umgebung zu schaffen, in der neuer Code in Zig und alter Code in C bleibt, dabei zu übersetzen oder zu interoperieren und sogar die Rolle eines C-Compilers zu übernehmen.
Es wird sehr gute Gründe geben, warum die Linux-Kernel-Maintainer Zig nicht statt Rust als C-Ersatz betrachten. Ich weiß nicht genug, um darüber zu spekulieren; es wäre gut, wenn Leute mit mehr Ahnung das erklären könnten.
Ich bin kein Kernel-Maintainer, aber wenn ich zwei wichtige Gründe vermuten müsste, warum Rust gegenüber Zig gewählt wurde, dann vielleicht die besseren Compile-Time-Garantien, die die Sprache bietet, und die schnellere Adoption.
Große Unternehmen der Branche arbeiten viel daran, Rust-nativen Code für APIs oder gepflegte Rust-Bindings bereitzustellen. Auch Windows-Entwickler schreiben Teile ihres eigenen Kernels in Rust neu. Das ist eine Bewegung, die schon ziemlich lange anhält, und ich hoffe, sie hört nicht auf.
Die Maintainer könnten das Gefühl haben, dass Zig gegenüber C nicht genügend Vorteile bietet. Viele von ihnen sind selbst Rust gegenüber noch ablehnend.
Zig hat eine deutlich bessere Interoperabilität mit C als Rust, ist aber nicht speichersicher und noch nicht stabilisiert. Die Zig-Adoption in der C-Welt wird wohl deutlich zunehmen, aber es ist schwer, es als direkten Konkurrenten zu Rust zu sehen.
In meiner Region führt niemand Rust ein, und C++-Leute bleiben bei C++. Anfangs gab es etwas Interesse an Rust, aber in keinem Unternehmen, das ich kenne, hat es sich etabliert. Das hat wahrscheinlich ähnliche Gründe wie bei Go, das in jungen Unternehmen stark gewachsen ist, aber in traditionelle Java-/C#-Unternehmen kaum hineinkommt. Selbst wenn es technisch sinnvoll ist, ist es eine enorme Change-Management-Aufgabe.
Zig gewinnt bei Programmen an Schwung, die keine dynamische Speicherallokation brauchen, aber viel mehr auch nicht.
Es gibt weiterhin regelmäßig Breaking Changes; das ist im Moment gut für Zig, aber nicht gut für eine riesige, langlebige Codebasis wie Linux. Auch Compiler-Bugs treten auf.
Das sage ich als jemand, der die Richtung von Zig im Großen und Ganzen mag.
Ich frage mich, ob ein Tool wie
C2Rustdies nutzen könnte, um formal korrekten Code zu erzeugen.Außerdem frage ich mich, wie viel Handarbeit die Autoren geleistet haben oder ob sie etwas ausgeführt haben, um Rust-Code zu generieren. Falls ja, weiß ich nicht, wo der Code ist, der Rust erzeugt, und ich sehe auch keinen Link zum Source-Repository.
Wenn eine C-Bibliothek funktioniert, also nicht formal als problemfrei bewiesen ist, aber meistens gut läuft, frage ich mich, warum man sie nicht mit
unsafe Rustübersetzt.Rust hat insgesamt zu wenige Bibliotheken, daher sehe ich darin Wert. Letztlich unterscheidet es sich nicht wesentlich davon, eine in C geschriebene dll/so zu verwenden, die in manchen Situationen unsicher sein kann.