Erste Schritte für Rust

Bild


Hallo an alle. Kürzlich habe ich eine neue Programmiersprache Rust kennengelernt. Ich bemerkte, dass er anders war als andere, denen ich zuvor begegnet war. Deshalb habe ich mich entschlossen, tiefer zu graben. Ich möchte die Ergebnisse und meine Eindrücke teilen:


  • Ich werde meiner Meinung nach mit den Hauptmerkmalen von Rust beginnen
  • Ich werde interessante Syntaxdetails beschreiben
  • Ich werde erklären, warum Rust wahrscheinlich nicht die Welt übernehmen wird

Ich werde sofort erklären, dass ich seit ungefähr zehn Jahren in Java schreibe, also werde ich von meinem Glockenturm aus streiten.


Killer-Feature


Rust versucht, eine Zwischenposition zwischen Low-Level-Sprachen wie C / C ++ und High-Level-Java / C # / Python / Ruby einzunehmen. Je näher die Sprache an der Hardware liegt, desto besser ist die Kontrolle über die Ausführung des Codes. Der vollständige Zugriff auf das Gedächtnis ist jedoch viel einfacher, um Ihr Bein zu schießen. Im Gegensatz zu C / C ++ erschienen Python / Java und alle anderen. Sie müssen nicht daran denken, das Gedächtnis zu löschen. Das Schlimmste ist NPE, Leckagen sind nicht so häufig. Damit dies funktioniert, benötigen Sie mindestens einen Garbage Collector, der seinerseits parallel zum Benutzercode sein eigenes Leben führt und dessen Vorhersehbarkeit verringert. Die virtuelle Maschine bietet weiterhin Plattformunabhängigkeit, aber wie viel benötigt wird, ist ein strittiger Punkt. Ich werde ihn jetzt nicht ansprechen.


Rust ist eine einfache Sprache. Der Compiler gibt eine Binärdatei aus, für deren Arbeit keine zusätzlichen Tricks erforderlich sind. Die gesamte Logik zum Entfernen unnötiger Objekte ist zum Zeitpunkt der Kompilierung in den Code integriert, d. H. Zur Laufzeit gibt es auch keinen Garbage Collector. Rust hat auch keine Nullreferenzen und Typen sind sicher, was es noch zuverlässiger als Java macht.


Im Zentrum der Speicherverwaltung steht die Idee, eine Objektreferenz zu besitzen und auszuleihen. Wenn jedes Objekt nur einer Variablen gehört, kann alles, auf das es zeigt, rekursiv gelöscht werden, sobald es am Ende des Blocks abläuft. Links können auch zum Lesen oder Schreiben ausgeliehen werden. Hier funktioniert das Prinzip eines Schriftstellers und vieler Leser.


Dieses Konzept kann im folgenden Code demonstriert werden. Test () wird von der main () -Methode aufgerufen, die eine rekursive Datenstruktur MyStruct erstellt , die die Destruktorschnittstelle implementiert. Mit Drop können Sie die Logik festlegen, die ausgeführt werden soll, bevor das Objekt zerstört wird. Ähnlich wie beim Finalizer in Java ist der Zeitpunkt des Aufrufs der drop () -Methode im Gegensatz zu Java ziemlich sicher.


fn main() { test(); println!("End of main") } fn test() { let a = MyStruct { v: 1, s: Box::new( Some(MyStruct { v: 2, s: Box::new(None), }) ), }; println!("End of test") } struct MyStruct { v: i32, s: Box<Option<MyStruct>>, } impl Drop for MyStruct { fn drop(&mut self) { println!("Cleaning {}", self.v) } } 

Die Schlussfolgerung lautet wie folgt:


 End of test Cleaning 1 Cleaning 2 End of main 

Das heißt, Vor dem Beenden von test () wurde der Speicher rekursiv gelöscht. Der Compiler hat sich darum gekümmert, indem er den erforderlichen Code eingefügt hat. Was Box und Option ist, wird etwas später beschrieben.


Auf diese Weise übernimmt Rust die Sicherheit von Hochsprachen und die Vorhersagbarkeit von Programmiersprachen auf niedriger Ebene.


Was sonst noch interessant


Als nächstes werde ich die Merkmale der Sprache meiner Meinung nach in absteigender Reihenfolge ihrer Wichtigkeit auflisten.


Oop


Hier ist Rust in der Regel den anderen voraus. Wenn die meisten Sprachen zu dem Schluss gekommen sind, dass die Mehrfachvererbung aufgegeben werden muss, gibt es in Rust überhaupt keine Vererbung. Das heißt, Eine Klasse kann nur Schnittstellen in beliebiger Anzahl implementieren, aber nicht von anderen Klassen erben. In Bezug auf Java würde dies bedeuten, dass alle Klassen endgültig sind. Im Allgemeinen ist die syntaktische Vielfalt für die Aufrechterhaltung der OOP nicht so groß. Vielleicht ist das das Beste.


Zum Kombinieren von Daten gibt es Strukturen, die eine Implementierung enthalten können. Schnittstellen werden als Merkmal bezeichnet und können auch Standardimplementierungen enthalten. Sie erreichen keine abstrakten Klassen, weil kann keine Felder enthalten, viele beschweren sich über diese Einschränkung. Die Syntax ist wie folgt, ich denke, die Kommentare werden hier nicht benötigt:


 fn main() { MyPrinter { value: 10 }.print(); } trait Printer { fn print(&self); } impl Printer { fn print(&self) { println!("hello!") } } struct MyPrinter { value: i32 } impl Printer for MyPrinter { fn print(&self) { println!("{}", self.value) } } 

Von den Merkmalen, die mir aufgefallen sind, ist Folgendes zu beachten:


  • Klassen haben keine Konstruktoren. Es gibt nur Initialisierer, die Werte für Felder in geschweiften Klammern angeben. Wenn Sie einen Konstruktor benötigen, erfolgt dies über statische Methoden.
  • Die Instanzmethode unterscheidet sich von der statischen durch die & self- Referenz als erstes Argument.
  • Klassen, Schnittstellen und Methoden können ebenfalls verallgemeinert werden. Im Gegensatz zu Java gehen diese Informationen zum Zeitpunkt der Kompilierung jedoch nicht verloren.

Etwas mehr Sicherheit


Wie gesagt, Rust achtet sehr auf die Zuverlässigkeit des Codes und versucht, die meisten Fehler bei der Kompilierung zu vermeiden. Aus diesem Grund wurde die Möglichkeit, Links leer zu machen, ausgeschlossen. Es erinnerte mich an die nullbaren Typen von Kotlin. Option wird verwendet, um leere Links zu erstellen. Genau wie in Kotlin schlägt der Compiler beim Versuch, auf eine solche Variable zuzugreifen, die Hände und zwingt zum Einfügen von Schecks. Der Versuch, den Wert ohne Überprüfung herauszuziehen, kann zu einem Fehler führen. Dies kann aber sicherlich nicht zufällig geschehen, wie zum Beispiel in Java.


Mir hat auch gefallen, dass alle Variablen und Klassenfelder standardmäßig unveränderlich sind. Hallo nochmal Kotlin. Wenn sich der Wert ändern kann, sollte dies explizit mit dem Schlüsselwort mut angegeben werden . Ich denke, der Wunsch nach Unveränderlichkeit verbessert die Lesbarkeit und Vorhersagbarkeit des Codes erheblich. Obwohl Option aus irgendeinem Grund veränderlich ist, habe ich dies nicht verstanden. Hier ist der Code aus der Dokumentation:


 let mut x = Some(2); let y = x.take(); assert_eq!(x, None); assert_eq!(y, Some(2)); 

Transfers


Rost heißt Enum . Nur zusätzlich zu einer begrenzten Anzahl von Werten können sie noch beliebige Daten und Methoden enthalten. Es ist also etwas zwischen Aufzählungen und Klassen in Java. Die Standard- Enum-Option in meinem ersten Beispiel gehört einfach zu diesem Typ:


 pub enum Option<T> { None, Some(T), } 

Für die Verarbeitung solcher Werte gibt es eine spezielle Konstruktion:


 fn main() { let a = Some(1); match a { None => println!("empty"), Some(v) => println!("{}", v) } } 

Und auch


Ich habe nicht vor, ein Lehrbuch über Rust zu schreiben, sondern möchte nur dessen Merkmale hervorheben. In diesem Abschnitt werde ich beschreiben, was sonst noch nützlich ist, aber meiner Meinung nach nicht so einzigartig:


  • Fans der funktionalen Programmierung werden nicht enttäuscht sein, es gibt Lambdas für sie. Der Iterator verfügt über Methoden zum Verarbeiten der Sammlung, z. B. filter und for_each . So etwas wie Java-Streams.
  • Das Übereinstimmungskonstrukt kann auch für komplexere Dinge als die normale Aufzählung verwendet werden , beispielsweise zum Verarbeiten von Mustern.
  • Es gibt eine große Anzahl integrierter Klassen, z. B. Sammlungen: Vec, LinkedList, HashMap usw.
  • Sie können Makros erstellen
  • Es ist möglich, Methoden zu vorhandenen Klassen hinzuzufügen
  • Automatische Typinferenz unterstützt
  • Zusammen mit der Sprache kommt ein Standard-Test-Framework
  • Das integrierte Frachtdienstprogramm wird zum Erstellen und Verwalten von Abhängigkeiten verwendet

Fliege in die Salbe


Dieser Abschnitt ist erforderlich, um das Bild zu vervollständigen.


Killerproblem


Der Hauptnachteil liegt in der Hauptfunktion. Sie müssen für alles bezahlen. In Rust ist es sehr unpraktisch, mit veränderlichen Graphdatenstrukturen zu arbeiten, weil Jedes Objekt sollte nicht mehr als einen Link haben. Um diese Einschränkung zu umgehen, gibt es eine Reihe integrierter Klassen:


  • Box - ein unveränderlicher Wert auf dem Heap, ein Analogon von Wrappern für Grundelemente in Java
  • Zelle - variabler Wert
  • RefCell - variabler Wert, auf den als Referenz zugegriffen werden kann
  • Rc - Referenzzähler für mehrere Verweise auf ein Objekt

Und das ist eine unvollständige Liste. Für das erste Rust-Beispiel habe ich mich rücksichtslos entschlossen, eine einfach verknüpfte Liste mit grundlegenden Methoden zu schreiben. Letztendlich führte die Verknüpfung mit dem Knoten zu der folgenden Option <Rc <RefCell <ListNode> >> :


  • Option - um einen leeren Link zu verarbeiten
  • Rc - für mehrere Links, as Der letzte Knoten wird vom vorherigen Knoten und dem Blatt selbst referenziert
  • RefCell - für veränderlichen Link
  • ListNode - das nächste Element selbst

Es sieht so lala aus, insgesamt drei Wrapper um ein Objekt. Der Code zum einfachen Hinzufügen eines Elements am Ende der Liste ist sehr umständlich, und es enthält nicht offensichtliche Dinge wie Klonen und Ausleihen:


 struct ListNode { val: i32, next: Node, } pub struct LinkedList { root: Node, last: Node, } type Node = Option<Rc<RefCell<ListNode>>>; impl LinkedList { pub fn add(mut self, val: i32) -> LinkedList { let n = Rc::new(RefCell::new(ListNode { val: val, next: None })); if (self.root.is_none()){ self.root = Some(n.clone()); } self.last.map(|v| { v.borrow_mut().next = Some(n.clone()) }); self.last = Some(n); self } ... 

Auf Kotlin sieht das viel einfacher aus:


 public fun add(value: Int) { val newNode = ListNode(null, value); root = root ?: newNode; last?.next = newNode last = newNode; } 

Wie ich später herausfand, sind solche Strukturen nicht typisch für Rust, und mein Code ist völlig nicht idiomatisch. Die Leute schreiben sogar ganze Artikel:



Hier opfert Rust die Lesbarkeit für die Sicherheit. Darüber hinaus können solche Übungen immer noch zu geloopten Links führen, die im Speicher hängen, weil Kein Müllsammler wird sie wegnehmen. Ich habe in Rust keinen Arbeitscode geschrieben, daher fällt es mir schwer zu sagen, wie sehr solche Schwierigkeiten das Leben erschweren. Es wäre interessant, Kommentare von praktizierenden Ingenieuren zu erhalten.


Schwierigkeiten beim Lernen


Der lange Prozess des Lernens von Rust folgt weitgehend aus dem vorherigen Abschnitt. Bevor Sie überhaupt etwas schreiben, müssen Sie Zeit damit verbringen, das Schlüsselkonzept des Speicherbesitzes zu beherrschen es durchdringt jede Linie. Zum Beispiel hat mich die einfachste Liste ein paar Abende gekostet, während auf Kotlin dasselbe in 10 Minuten geschrieben ist, obwohl dies nicht meine Arbeitssprache ist. Darüber hinaus sehen viele bekannte Ansätze zum Schreiben von Algorithmen oder Datenstrukturen in Rust anders aus oder funktionieren überhaupt nicht. Das heißt, Wenn Sie darauf umsteigen, ist eine tiefere Umstrukturierung des Denkens erforderlich. Es reicht nicht aus, nur die Syntax zu beherrschen. Dies ist weit entfernt von JavaScript, das alles verschluckt und aushält. Ich denke, Rust wird niemals die Sprache sein, in der Kinder in einer Programmierschule unterrichtet werden. Auch C / C ++ hat in diesem Sinne mehr Chancen.


Zusammenfassend


Ich fand die Idee, den Speicher in der Kompilierungsphase zu verwalten, sehr interessant. In C / C ++ habe ich keine Erfahrung, daher werde ich nicht mit Smart Pointer vergleichen. Die Syntax ist im Allgemeinen angenehm und es gibt nichts Überflüssiges. Ich habe Rust für die Komplexität der Implementierung von Graphendatenstrukturen kritisiert, aber ich vermute, dass dies ein Merkmal aller Nicht-GC-Programmiersprachen ist. Vielleicht war der Vergleich mit Kotlin nicht ganz ehrlich.


Todo


In diesem Artikel habe ich Multithreading überhaupt nicht angesprochen, ich denke, dies ist ein separates großes Thema. Es gibt jedoch Pläne, eine Datenstruktur oder einen Algorithmus zu schreiben, der komplizierter als die Liste ist. Wenn Sie Ideen haben, teilen Sie diese bitte in den Kommentaren mit. Es wäre interessant zu wissen, welche Arten von Anwendungen im Allgemeinen in Rust geschrieben sind.


Lesen Sie


Wenn Sie an Rust interessiert sind, hier ein paar Links:



UPD: Vielen Dank für Ihre Kommentare. Ich habe viele nützliche Dinge für mich gelernt. Korrigierte Ungenauigkeiten und Tippfehler, fügte Links hinzu. Ich denke, solche Diskussionen tragen wesentlich zum Studium neuer Technologien bei.

Source: https://habr.com/ru/post/de433302/


All Articles