[RUBY] Die Sprachen, die Rust beeinflusst haben

Bei der Verwendung von Rust denke ich manchmal ** "Was? Diese Funktion sieht nicht wie andere Sprachen aus?" **. Das erste, woran ich dachte, waren Eigenschaften, die ich als äquivalent zu Haskells Typklassen und Scalas Implicits-Typklassenmustern verstand. Ich hatte das Gefühl, dass die Abschlusssyntax der Blocknotation von Ruby ähnlich war. Ich wollte etwas tiefer in solche ** "ähnlichen" ** eintauchen, also habe ich mir die Sprachen angesehen, die Rust beeinflusst haben.

(Dieser Artikel ist ein Nachdruck aus Mein Blog.)

TL;DR

Von Rust betroffene Sprachen

Ich habe die unter "Einflüsse" aufgeführten Sprachen [^ 1] in chronologischer Reihenfolge angeordnet und eine Matrix der Merkmale der Sprachen erstellt. Für die Auswahl der Merkmale wird das Paradigma ausgewählt, auf das sich Rust mit Ausnahme von GC konzentriert. Zum Vergleich wird auch Rust selbst hinzugefügt.

Aussehen Alter FP OOP Parallele Berechnung Statische Eingabe Parameter mehrphasig Ad-hoc-Polymorph GC
C 1972 o
Scheme 1975 o o
C++ 1983 o o o
Newsqueak 1984 o o o
Erlang 1986 o o o
SML 1990 o o o o
Haskell 1990 o o o o o
Alef 1992 o o o
Limbo 1995 o o o
Ruby 1995 o o
OCaml 1996 o o o o o
ML Kit 1997[^2] o o o △[^3]
C# 2000 o o o o
Cyclone 2006 o o o △[^4]
Rust 2010 o o o o o o
Swift 2014 o o o o o[^5]

Die Bedeutung jeder Spalte ist wie folgt. Die Merkmale der Sprache basieren hauptsächlich auf Wikipedia. Bitte beachten Sie jedoch, dass eine genaue Klassifizierung schwierig ist und Dogmatismus und Vorurteile beinhaltet.

[^ 1]: Der Unicode-Anhang ist keine Sprache, daher habe ich ihn ausgeschlossen. NIL und Hermes waren Auswirkungen auf Funktionen, die bereits eingestellt wurden, daher habe ich sie auch ausgeschlossen. Die Sprache C wurde hinzugefügt, weil sie in Warum Rost? - # Einflüsse | Lernrost beschrieben wurde. Ich war besorgt darüber, ob ich F # einschließen sollte, konnte es aber nicht einschließen, da es schwierig war, die Funktionen, die es speziell betrafen, nur mit "Functional Programming" zu verstehen. [^ 2]: Basierend auf dem Veröffentlichungsjahr von Programmieren mit Regionen im ML-Kit Ich werde. [^ 3]: Einige ML Kit-Implementierungen verfügen über eine region-basierte Speicherverwaltung mit GC, daher ist sie als △ gekennzeichnet. (Referenzen) [^ 4]: Da die Verwendung von GC optional ist, ist es mit △ gekennzeichnet. Insbesondere können Sie die Garbage Collection für Heap-Regionen verwenden. [^ 5]: Swift verwendet ARC (Automatic Reference Counting). Es ist fraglich, ob ARC als GC klassifiziert ist, aber in diesem Artikel wird es aufgrund des vorhandenen Laufzeitaufwands von ARC als "GC" klassifiziert.

Von Rust betroffene Funktionen

Von hier aus möchte ich mir jede der betroffenen Funktionen ansehen.

Algebraische Datentypen

Algebraische Datentypen werden häufig in Sprachen verwendet, die die funktionale Programmierung unterstützen. Dies ist die Summe der direkten Produkttypen. In Rust können algebraische Datentypen mithilfe von Enum realisiert werden.

rust


enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

Das Obige drückt den IP-Adresstyp aus, aber der V4-Typ ist ein direktes Produkt vom Typ u8, und die Summe aus V4-Typ und V6-Typ ist IpAddr-Typ.

Mustervergleich

Grob gesagt ist Pattern Matching eine erweiterte Version gängiger Verzweigungsstrukturen wie "if" und "switch". Pattern Match ist in Rust mit match verfügbar.

rust


let x = Some(2);
match x {
    Some(10) => println!("10"), // `Option`Die Form wird zerlegt und angepasst
    Some(y) => println!("x = {:?}", y),
    _ => println!("error!"),
}

Der Mustervergleich kann durch Zerlegen der Datenstruktur wie im obigen Code abgeglichen werden, sodass er mit algebraischen Datentypen kompatibel ist.

In Bezug auf den Mustervergleich habe ich einen Artikel "Allen Programmierern gewidmet! Illustrierter" Mustervergleich "" geschrieben. Bitte beziehen Sie sich auch darauf.

Typinferenz

Die Typinferenz gewinnt allmählich an Popularität, da sie von Java und C # in statisch typisierten Sprachen übernommen wurde. Die Typinferenz von Rust basiert auf einer ** leistungsstarken Hindley-Milner-Typinferenz **, und der vorherige Typ kann aus einem Rückwärtsausdruck oder einer Anweisung abgeleitet werden.

rust


let mut x = HashMap::new(); //HashMap-Schlüssel- und Werttypen werden aus nachfolgenden Ausdrücken und Anweisungen abgeleitet. Praktisch!

x.insert(String::from("Key"), 10);
println!("{:?}", x);

Im obigen Beispiel werden die HashMap-Schlüssel- und Werttypen aus den Argumenten der nachfolgenden Einfügefunktion abgeleitet. Dies ist mit Java- oder C # -Typ-Inferenz nicht möglich. Dies liegt daran, dass die in diesen Sprachen eingeführten Typinferenzen lokale variable Typinferenzen sind und sich von Rusts Typinferenzen unterscheiden.

Semikolon-Anweisungstrennung

Rostfunktionen bestehen hauptsächlich aus "Anweisungen" und enden ganz am Ende mit "Anweisungen" oder "Ausdrücken". Rostanweisungen werden durch ein Semikolon ";" getrennt. Daher ist es möglich, zwischen einer Anweisung und einem Ausdruck zu unterscheiden, je nachdem, ob am Ende ein Semikolon hinzugefügt wird oder nicht.

rust


fn test1() -> i32 {
    let mut x = 2; //Satz
    x =  x * 3 ;   //Satz
    x + 5          //Ausdruck (Rückgabewert ist 11)
}

fn test2() -> () {
    let mut x = 2; //Satz
    x =  x * 3 ;   //Satz
    x + 5;         //Anweisung (Rückgabewert ist Einheit`()`)
}

Das Interessante an Rust ist, dass Sie es einfach durch Hinzufügen eines Semikolons ";" nach jedem "Ausdruck" in einen "Satz" umwandeln können. Und wenn es um Anweisungen geht, ist der Rückgabewert am Ende der Funktion die Einheit (). Mit dem obigen Code erklärt, ändert sich der Rückgabewert abhängig davon, ob in der letzten Zeile ein Semikolon (;) steht [^ 6]. Mit anderen Worten, in Rust kann eine "Anweisung" auch als eine Art "Ausdruck" betrachtet werden, der einen "Wert" zurückgibt. Rust wird auch als ** ausdrucksorientierte Sprache ** bezeichnet, da Kontrollstrukturen wie "if", "match" und "while" ebenfalls Werte zurückgeben.

Die betroffene Sprache OCaml setzt auch "Ausdrücke" an die Basis von Programmkomponenten und trennt sie durch Semikolons.

--Verweise

[^ 6]: Wenn der Rückgabewert die Einheit () ist, können Sie den Rückgabewert in der Funktionssignatur weglassen. Fn test2-> () {...} ist also gleichbedeutend mit fn test2 () {...}.

Verweise

Referenzen können durch Hinzufügen von & zu Variablen erstellt werden, und "Aliase" können für Variablen erstellt werden. Es ähnelt C-Zeigern, außer dass ** Nullzeiger nicht existieren **. Mit anderen Worten wird angenommen, dass es immer ein Referenzziel gibt. Das Referenzziel kann mit dem Dereferenzierungsoperator "*" referenziert werden. Im Fall einer Variablen (mit "mut") kann das Referenzziel neu geschrieben werden.

rust


let mut x = "hoge".to_string();
let y = &mut x;
println!("y = {}", *y); // hoge
*y = "mohe".to_string(); // `*y`Schreiben Sie x neu, um es neu zu schreiben
println!("x = {}", x); // mohe

Diese Referenz wird als charakteristisches Merkmal von C ++ angesehen und ist zweifellos ein Einfluss von C ++, es gibt jedoch auch große Unterschiede. Es ist eine Verbindung zu Rusts "Besitz und Lebensdauer", und im Gegensatz zu C ++ sind Rust-Referenzen niemals ** baumelnde Referenzen **.

RAII(Resource Acquisition Is Initialization)

RAII bedeutet wörtlich "Ressourcenzuweisung ist (variable) Initialisierung". Um dieses Konzept besser zu verstehen, erscheint es jedoch dringender, es als ** "Ressourcen freizusetzen ist die Zerstörung von Variablen" ** zu betrachten. Ein typisches Beispiel für eine gemeinsame Ressource ist der Speicher, der beim Initialisieren der Variablen zugewiesen und beim Zerstören der Variablen freigegeben wird. Der Ausdruck RAII wird in Rust selten offen verwendet, aber er wird in die Idee von ** "Eigentum" ** und ** "Umfang" ** aufgenommen.

rust


{
    let x = 10; //Zu diesem Zeitpunkt wird die Variable x initialisiert und der Speicher gesichert.
    {
        let y = "hoge".to_string(); //Zu diesem Zeitpunkt wird die Variable y initialisiert und der Speicher gesichert.

        //Verarbeite verschiedene Dinge

    } //Hier verlässt die Variable y den Gültigkeitsbereich und wird zerstört, wodurch Speicher frei wird

    //Verarbeite verschiedene Dinge

} //Hier verlässt die Variable x den Gültigkeitsbereich und wird zerstört, wodurch Speicher frei wird

Im obigen Codebeispiel reicht der gültige Bereich der Variablen von der Initialisierung der Variablen bis zum Ende des innersten Bereichs (dem Bereich, der in der mittleren Klammer "{}" eingeschlossen ist). In Sprachen mit Garbage Collection (GC) wie Java und Ruby wird der Speicher auch nach Zerstörung einer Variablen erst freigegeben, wenn der Garbage Collector den Speicher zurückerobert [^ 7]. Im obigen Codebeispiel wird der Speicher auch als Ressource verwendet, aber anders als der Speicher kann er mit "Verwenden" und "Zurückgeben" verknüpft sein, z. B. Öffnen und Schließen von Dateien. Tatsächlich verwenden viele Standardbibliotheken von Rust RAII.

[^ 7]: Selbst in Java werden primitive Typen usw. im Stapel reserviert, wenn sie als lokale Variablen deklariert werden und die Anforderungen von RAII erfüllen. Die meisten anderen Objekte sind jedoch im Heap reserviert und unterliegen der Speicherbereinigung. Werden.

Intelligente Zeiger

Ein intelligenter Zeiger ist eine Art Zeiger, der nicht nur auf eine Speicheradresse zeigt, sondern auch ** zusätzliche Funktionen ** hat. Der Smart-Zeiger in Rust ist derjenige, der den Speicher des Heaps ** "smart" ** wie String und Vec im Standardbibliothekstyp zuweist und freigibt. Der Schlüssel zur Unterscheidung von intelligenten Zeigern in Rust ist, ob sie ** "Deref-Merkmale" ** oder ** "Drop-Merkmale" ** implementieren.

rust


{
    let a = String::from("hoge"); //String"hoge"Ist auf dem Haufen reserviert
    let b = vec![1, 2, 3]; //Vektor wird in Heap zugeordnet
    let c = Box::new(5); //Die Ganzzahl vom Typ i32 wird im Heap zugewiesen
} //Variable a, b,c wird zerstört und gleichzeitig wird der im Heap zugewiesene Speicher freigegeben

Semantik verschieben

Grob gesagt bedeutet Verschiebungssemantik, dass ** Besitz übertragen wird **, wenn einer Variablen ein Wert zugewiesen oder eine Funktion als Argument übergeben wird.

rust


let s1 = "Rust Life".to_string();
println!("{}", s1); // OK

let s2 = s1; //Bewegungssemantik: Eigentum`s1`Von`s2`Zieht zu

println!("{}", s2); // OK
println!("{}", s1); //Kompilierungsfehler: Eigentümer`s2`Weil es umgezogen ist`s1`Nicht zugänglich für

Im obigen Code ist let s2 = s1; die Bewegungssemantik. Dies liegt daran, dass Rust das Eigentum an einem Wert immer auf eins beschränkt.

Monomorphisierung

Rust-Generika werden zur Kompilierungszeit auf bestimmte im Programm verwendete Typen erweitert, die als "Monophase" bezeichnet werden. Da "Monophase" die Funktion bestimmt, die zur Kompilierungszeit aufgerufen werden soll, handelt es sich um einen ** statischen Versand **, und mit der Abstraktion ist kein Laufzeitaufrufaufwand verbunden.

In dem betroffenen C ++ wird Monophasisierung als Template-Instanziierung und Spezialisierung bezeichnet.

Speichermodell

Das Speichermodell hat mehrere Auswirkungen, aber das Speichermodell in diesem Zusammenhang befasst sich mit der ** Konsistenz des Zugriffs auf gemeinsam genutzten Speicher in einer Multithread-Umgebung **. Im Allgemeinen gibt es ** "atomare Operationen" **, die sicher vor Multithreading betrieben werden können, aber ein Speichermodell ist erforderlich, um diese zu realisieren. Weitere Informationen finden Sie in der folgenden Rust-Dokumentation.

Regionsbasierte Speicherverwaltung

Die bereichsbasierte Speicherverwaltung unterteilt den Speicher in Bereiche, die als "Regionen" bezeichnet werden, und verwaltet den Speicher in Verbindung mit dem Typsystem weiter. In Rust scheint es stark in die lebenslange Verwaltung von Referenzen involviert zu sein.

--Verweise - Region-Based Memory Management in Cyclone - Cyclone: Memory Management Via Regions

Typklassen, Typfamilien

"Typklasse" ist ein von Haskell abgeleitetes Wort, und die entsprechende Funktion in Rust ist ** Merkmal **, mit dem das Verhalten definiert wird, das ** Typen ** gemeinsam ist. Es gibt etwas in der Nähe der Java-Schnittstelle, aber die Funktion besteht darin, dass Sie das Merkmal implementieren können, nachdem der Typ definiert wurde, und nicht, wenn der Typ definiert ist.

rust


trait Greeting { //Definition des Merkmals
    fn greet(&self) -> String;
}

fn print_greet<T: Greeting>(person: T) { //Funktioniert mit Merkmalsgrenzen
    println!("{}!", person.greet());
}

struct Japanese { name: String, }        // `struct`Typdefinition mit
struct American { name: String, age: u32,}

impl Greeting for Japanese { //Implementierung von Merkmalen
    fn greet(&self) -> String { "Hallo".to_string() }
}

impl Greeting for American {
    fn greet(&self) -> String { "Hello".to_string() }
}

impl Greeting for i32 { //Sie können Merkmale auch für integrierte Typen implementieren!
    fn greet(&self) -> String { self.to_string() }
}

fn main() {
    let person_a = Japanese {name: "Taro".to_string(),};
    let person_b = American {name: "Alex".to_string(), age: 20,};

    // print_Die Begrüßungsfunktion kann für verschiedene Typen aufgerufen werden, die die Begrüßung implementieren(Ad-hoc-Polymorph)
    print_greet(person_a);
    print_greet(person_b);
    print_greet(123);
}

Wie im obigen Code erläutert, ist die Funktion "print_greet ()" eine Funktion, die aufgerufen werden kann, wenn das Merkmal "Begrüßung" implementiert ist. Und wenn der Typ "Japanisch" bereits definiert ist, können Sie ihn mit der Funktion "print_greet ()" aufrufen, indem Sie das Merkmal "Begrüßung" ("impl Greeting for Japanese") implementieren. Interessant ist, dass Merkmale an eingebauten Typen wie "i32" nachgerüstet werden können. Die Eigenschaft einer Funktion, die die später zu übergebenden Typen erhöhen kann, wie z. B. diese Funktion print_greet (), wird als ** Ad-hoc-Polymorphismus ** bezeichnet.

Grob gesagt ist "Typfamilie" eine Funktion, die eine Typfunktion realisiert, die einen Typ empfängt und den Typ zurückgibt. In Rust besteht eine Verbindung mit ** "verwandter Typ" **. Die Definition und das Verwendungsbeispiel stammen aus der Standardbibliothek "Hinzufügen".

rust


pub trait Add<Rhs = Self> {
    type Output; //Verwandter Typ
    fn add(self, rhs: Rhs) -> Self::Output;
}

struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Self; //Verwandter Typ

    fn add(self, other: Self) -> Self {
        Self {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
           Point { x: 3, y: 3 });

Verwandte Typen werden im Merkmal mit "Typ" deklariert, wie im obigen Code. Sie können etwas Ähnliches wie Generics tun, aber es ist auch nützlich. Wenn Sie interessiert sind, überprüfen Sie bitte die folgenden Referenzen für detaillierte Situationen.

Kanäle, Parallelität

Kanäle sind Grundelemente für die asynchrone Kommunikation. ** Sender und Empfänger können Daten asynchron über den Kanal übertragen **. Der folgende Code ist ein Codeteil aus Channel --Rust By Example Japanische Version ( Kommentare geändert zu Originalkommentaren).

rust


use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc;
use std::thread;

static NTHREADS: i32 = 3;

fn main() {
    let (tx, rx): (Sender<i32>, Receiver<i32>) = mpsc::channel(); //Kanal erstellen
    let mut children = Vec::new();

    for id in 0..NTHREADS {
        let thread_tx = tx.clone();

        let child = thread::spawn(move || { //Thread erstellen
            thread_tx.send(id).unwrap(); //Senden von Daten über den Kanal
            println!("thread {} finished", id);
        });

        children.push(child);
    }

    let mut ids = Vec::with_capacity(NTHREADS as usize);
    for _ in 0..NTHREADS {
        ids.push(rx.recv()); //Empfangen von Daten, die von untergeordneten Threads gesendet wurden
    }

    for child in children {
        child.join().expect("oops! the child thread panicked");
    }

    println!("{:?}", ids);
}

Der obige Code ist der Code, der einen Kanal erstellt, an den untergeordneten Thread weiterleitet, Daten vom untergeordneten Thread über den Kanal sendet und vom übergeordneten Thread empfängt.

Nachrichtenübermittlung, Thread-Fehler

Ich konnte es nicht herausfinden, also werde ich es weglassen.

Optionale Bindung

Die optionale Bindung ist eine Swift-Funktion, die, wie der Name schon sagt, eine Variable bindet und einen Codeblock ausführt, wenn ein optionaler Wert vorhanden ist. Die entsprechende Funktion von Rust ist "if let", es stehen jedoch verschiedene Musterübereinstimmungen zur Verfügung, die nicht auf "Option" beschränkt sind.

rust


let num = Some(10);

if let Some(i) = num {
    println!("num =  {}", i);
}

Hygienemakros

Ein Hygienemakro ist ein Makro, das garantiert, dass der im Makro eingeführte Variablenname und der Variablenname des Makroaufrufers nicht in Konflikt stehen. Unten finden Sie einen Beispielcode für ein einfaches Rust-Makro.

rust


macro_rules! my_macro { //Makro
    ($x:expr) => {
        {
            let a = 2;
            $x + a
        }
    };
}

fn main() {
    let a = 5;
    println!("{}", my_macro!(a)); // 7
}

Rostmakros sind hygienisch. Selbst wenn dem Makro my_macro! Eine Variable a übergeben wird, wird es anders behandelt als die Variable a, die im internen let eingeführt wurde. Dies kann zu Konflikten mit C- und Lisp-Makros führen, daher musste ich absichtlich Variablen auswählen, die nicht in Konflikt stehen würden.

Attribute

Attribute sind hauptsächlich ** zusätzliche Informationen (Metadaten) **, die der Deklaration hinzugefügt wurden. Ein häufiger Anblick in Rust ist das Attribut "# [test]", das einen Komponententest kennzeichnet.

rust


#[test] //Attribut(Testfunktionsmarkierung)
fn test_hoge() {
    // test code
}

#[allow(dead_code)] //Attribut(Warnung Unterdrückung nicht verwendeter Funktionen)
fn foo() {}

#[derive(Debug, Clone, Copy, Default, Eq, Hash, Ord, PartialOrd, PartialEq)] //Attribut(Automatische Implementierung von Merkmalen)
struct Num(i32);

Abschlusssyntax

Wenn Sie die Blocknotation von Ruby und die Abschlussnotation von Rust vergleichen, können Sie feststellen, dass sie ähnlich sind.

ruby


ia = [1,2,3]

ia.each {|e| puts e } #Rubinblock(`each`Argumente von)

rust


let ia = [1, 2, 3];

ia.iter().for_each(|e| println!("{}", e)); //Rostverschluss(`for_each`Argumente von)

Wirkungsvisualisierung

Ich habe versucht, die Sprachen zu visualisieren, die Rust beeinflusst haben. Die Sprachen sind grob im Uhrzeigersinn in chronologischer Reihenfolge angeordnet. Farben werden grob nach FP, OOP, paralleler Berechnung und anderen kategorisiert.

Wenn wir uns diese Abbildung ansehen, können wir sehen, wie die Sprachen verschiedener Paradigmen ausgewogen beeinflusst werden.

Zusammenfassung

Ich habe die Sprachen, die Rust beeinflusst haben, grob in eine Tabelle eingeteilt und versucht, sie weiter zu visualisieren. Wir haben auch eine grobe Einführung in die einzelnen Merkmale gegeben, die es beeinflusst haben. Die Beschreibung von Einflüsse der ursprünglichen Rust-Referenz lautet wie folgt.

Auf den ersten Blick scheint Rust mit vielen erweiterten Funktionen ausgestattet zu sein, aber viele davon basieren auf Forschungsergebnissen und bewährten Sprachen. Sogar die Besitzsysteme und Kreditprüfer, über die häufig als Merkmal von Rust gesprochen wird, werden stark von Sprachen wie C ++, ML Kit und Cyclone beeinflusst. Und als ich die einzelnen Einflüsse untersuchte, war es interessant zu sehen, dass ** Rust eine Pause vom vollständigen GC erzielen konnte, die mit ML Kit oder Cyclone ** nicht erreicht werden konnte.

Ich dachte nicht, dass so viele Funktionen aus anderen Sprachen stammen, bis ich sie nachgeschlagen habe. Das Alter und die Paradigmen der betroffenen Sprachen sind ebenfalls unterschiedlich, und während ich Rust erforschte, hatte ich das Gefühl, die Geschichte der Sprachentwicklung zu lernen. Und ich habe das Gefühl, dass Rust uns den Prozess gezeigt hat, die Mängel jeder Sprache zu überwinden und die guten Punkte erfolgreich zu integrieren.

Es ist wahr, wenn Sie von einer solchen Sprache beeinflusst werden, wird gesagt, dass die Lernkurve für Anfänger steil ist, aber wenn Sie umgekehrt darüber nachdenken, ist es eine Zusammenstellung der verschiedenen Sprachen, die beeinflusst wurden. Während ich dies schrieb, dachte ich, dass man sagen könnte, dass es eine sehr lohnende und profitable Sprache ** ist, die man lernen kann.

Wir hoffen, dieser Artikel hilft denen, die sich für Rust interessieren.

Verweise

Recommended Posts

Die Sprachen, die Rust beeinflusst haben
Dies und das von JDK
Ein Versuch, "Mathe-Rätsel, die das Rust-Gehirn mehr trainieren".
"Mathe-Rätsel, die das Programmhirn mehr trainieren" _Q39 (Code: Ruby) -> Rust
"Mathe-Rätsel, die das Programmhirn mehr trainieren" _pp.018-020 (Code: Ruby) -> Rust
"Mathe-Rätsel, die das Programmhirn mehr trainieren" _Q02 (Code: Ruby) -> Rust
"Mathe-Rätsel, die das Programmhirn mehr trainieren" _Q17 (Code: Ruby) -> Rust
"Mathe-Rätsel, die das Programmhirn mehr trainieren" _Q01 (Code: Ruby) -> Rust