[RUBY] Les langues qui ont influencé Rust

Lorsque j'utilise Rust, je pense parfois ** "Quoi? Cette fonctionnalité ne ressemble pas aux autres langues?" **. La première chose à laquelle j'ai pensé était les traits, que j'ai compris comme étant équivalents aux classes de type de Haskell et aux modèles de classe de type Implicits de Scala. J'ai senti que la syntaxe de fermeture était similaire à la notation par blocs de Ruby. Je voulais creuser un peu plus profondément dans ces ** «similaires» **, alors j'ai examiné les langues qui ont influencé Rust.

(Cet article est une réimpression de Mon blog.)

TL;DR

Langues affectées par la rouille

J'ai arrangé les langues [^ 1] listées dans "Influences" par ordre chronologique et ai fait une matrice des caractéristiques des langues. Pour la sélection des fonctionnalités, le paradigme sur lequel Rust se concentre est sélectionné à l'exception de GC. Rust lui-même est également ajouté à titre de comparaison.

Âge d'apparence FP OOP Calcul parallèle Typage statique Paramètre polyphase Polymorphe ad hoc 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]

La signification de chaque colonne est la suivante. Les caractéristiques de la langue sont principalement basées sur Wikipédia, mais veuillez noter qu'elle contient du dogmatisme et des préjugés car il est difficile de la classer avec précision.

--Date d'apparition ―― C'est l'âge où les langages de programmation sont apparus. Veuillez ignorer l'erreur de 3 ans avant et après --FP (programmation fonctionnelle) --Indique si la langue prend fortement en charge FP --Indique un △ s'il prend en charge modérément --OOP (programmation orientée objet) --Indique si la langue prend fortement en charge la POO --Calcul parallèle --Indique si le langage prend fortement en charge les caractéristiques des acteurs et des modèles de calcul CSP / π.

[^ 1]: L'annexe Unicode n'est pas un langage, je l'ai donc exclue. NIL et Hermes étaient des effets sur des fonctionnalités qui ont déjà été abandonnées, je les ai donc également exclues. Le langage C a été ajouté car il a été décrit dans Why Rust? - # Influences | Learning Rust. Je m'inquiétais de savoir s'il fallait inclure F #, mais je ne pouvais pas l'inclure car il était difficile de comprendre les fonctions qui l'affectaient spécifiquement avec uniquement la "Programmation fonctionnelle". [^ 2]: Basé sur l'année de publication de Programming with Regions in the ML Kit Je vais. [^ 3]: Certaines implémentations de ML Kit ont une gestion de la mémoire basée sur la région avec GC, elle est donc marquée △. (Références) [^ 4]: Étant donné que l'utilisation de GC est facultative, elle est marquée d'un △. Plus précisément, vous pouvez utiliser le garbage collection pour les régions de tas. [^ 5]: Swift utilise l'ARC (Automatic Reference Counting). On peut se demander si ARC est classé comme GC, mais dans cet article, il est classé comme "GC" en raison de l'existence d'une surcharge d'exécution d'ARC.

Fonctionnalités affectées par la rouille

À partir de là, je voudrais examiner chacune des fonctions affectées.

Types de données algébriques

Les types de données algébriques sont largement utilisés dans les langages qui prennent en charge la programmation fonctionnelle. C'est le total du type de produit direct. Dans Rust, les types de données algébriques peuvent être réalisés en utilisant Enum.

rust


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

Ce qui précède exprime le type d'adresse IP, mais le type V4 est un produit direct du type u8, et la somme du type V4 et du type V6 est de type IpAddr.

--Langue affectée - SML, OCaml

Correspondance de motif

En gros, la correspondance de motifs est une version améliorée des structures de branchement courantes telles que «if» et «switch». La correspondance de motif est disponible en rouille avec match.

rust


let x = Some(2);
match x {
    Some(10) => println!("10"), // `Option`Le moule est démonté et assorti
    Some(y) => println!("x = {:?}", y),
    _ => println!("error!"),
}

La correspondance de modèle peut être mise en correspondance en décomposant la structure de données comme dans le code ci-dessus, elle est donc compatible avec les types de données algébriques.

En ce qui concerne la correspondance de motifs, j'ai écrit un article "Dédié à tous les programmeurs!" Correspondance de motifs "illustrée", veuillez donc vous y référer également.

Inférence de type

L'inférence de type gagne progressivement en popularité, car elle a été adoptée par Java et C # dans les langages à typage statique. L'inférence de type de Rust est basée sur ** une puissante inférence de type Hindley-Milner **, et le type précédent peut être déduit d'une expression ou d'une instruction vers l'arrière.

rust


let mut x = HashMap::new(); //Les types de clé et de valeur HashMap sont déduits des expressions et instructions suivantes. Pratique!

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

Dans l'exemple ci-dessus, les types de clé et de valeur HashMap sont déduits des arguments de la fonction d'insertion suivante. Cela n'est pas possible avec l'inférence de type Java ou C #. C'est parce que les inférences de type introduites dans ces langages sont des inférences de type variable locale et sont différentes des inférences de type de Rust.

--Langue affectée - SML, OCaml

Séparation des instructions par point-virgule

Les fonctions Rust sont principalement composées de "déclarations" et se terminent par des "déclarations" ou des "expressions" à la toute fin. Les instructions Rust sont séparées par un point-virgule ";". Par conséquent, il est possible de faire la distinction entre une instruction et une expression selon que l'on ajoute ou non un point-virgule à la fin.

rust


fn test1() -> i32 {
    let mut x = 2; //Phrase
    x =  x * 3 ;   //Phrase
    x + 5          //Expression (la valeur de retour est 11)
}

fn test2() -> () {
    let mut x = 2; //Phrase
    x =  x * 3 ;   //Phrase
    x + 5;         //Instruction (la valeur de retour est l'unité`()`)
}

La chose intéressante à propos de Rust est que vous pouvez le convertir en "phrase" simplement en ajoutant un point-virgule ";" après chaque "expression". Et quand il s'agit d'instructions, la valeur de retour lorsqu'elle est placée à la fin de la fonction est l'unité (). En expliquant avec le code ci-dessus, la valeur de retour change selon qu'il y a ou non un point-virgule (;) dans la dernière ligne [^ 6]. En d'autres termes, dans Rust, une "instruction" peut également être considérée comme un type d '"expression" qui renvoie une "valeur". Rust est également appelé un ** langage orienté expression ** car les structures de contrôle telles que «if», «match» et «while» renvoient également des valeurs.

Le langage affecté, OCaml, place également des «expressions» à la base des composants du programme et les sépare par des points-virgules.

--Les références

[^ 6]: Si la valeur de retour est l'unité (), vous pouvez omettre la valeur de retour de la signature de la fonction. Donc fn test2-> () {...} est synonyme de fn test2 () {...}.

Les références

Les références peuvent être générées en ajoutant & aux variables, et des "alias" peuvent être créés pour les variables. Il est similaire à un pointeur en langage C, sauf que ** le pointeur nul n'existe pas **. En d'autres termes, on suppose qu'il existe toujours une destination de référence. La destination de référence peut être référencée à l'aide de l'opérateur de déréférencement "*". Dans le cas d'une variable (avec «mut»), la destination de référence peut être réécrite.

rust


let mut x = "hoge".to_string();
let y = &mut x;
println!("y = {}", *y); // hoge
*y = "mohe".to_string(); // `*y`Réécrire x pour réécrire
println!("x = {}", x); // mohe

Cette référence est considérée comme une caractéristique du C ++ et est sans aucun doute une influence du C ++, mais il existe également des différences majeures. C'est un lien vers "la propriété et la durée de vie" de Rust, et contrairement au C ++, les références Rust ne sont jamais ** des références pendantes **.

--Langue affectée - C++

RAII(Resource Acquisition Is Initialization)

RAII signifie littéralement "l'allocation des ressources est une initialisation (variable)". Cependant, afin de mieux comprendre ce concept, il semble plus urgent de le considérer comme ** «libérer des ressources, c'est détruire des variables» **. Un exemple typique de ressource commune est la mémoire, qui est allouée lorsque la variable est initialisée et libérée lorsque la variable est détruite. L'expression RAII est rarement utilisée ouvertement dans Rust, mais elle est incorporée dans l'idée de ** "propriété" ** et de ** "portée" **.

rust


{
    let x = 10; //À ce stade, la variable x est initialisée et la mémoire est sécurisée.
    {
        let y = "hoge".to_string(); //À ce stade, la variable y est initialisée et la mémoire est sécurisée.

        //Traiter diverses choses

    } //Ici, la variable y sort du champ d'application et est détruite, libérant de la mémoire

    //Traiter diverses choses

} //Ici, la variable x sort du champ d'application et est détruite, libérant de la mémoire

Dans l'exemple de code ci-dessus, la portée de la variable va de l'initialisation de la variable à la fin de la portée la plus interne (la plage entourée par le crochet du milieu {}). Dans les langages avec garbage collection (GC) tels que Java et Ruby, même après la destruction d'une variable, la mémoire n'est pas libérée tant que le garbage collector n'a pas récupéré la mémoire [^ 7]. De plus, dans l'exemple de code ci-dessus, la mémoire est utilisée comme ressource, mais à part la mémoire, elle peut être liée à «utiliser» et «retourner», comme l'ouverture et la fermeture de fichiers. En fait, de nombreuses bibliothèques standard de Rust utilisent RAII.

[^ 7]: Même en Java, les types primitifs, etc. sont réservés dans la pile lorsqu'ils sont déclarés en tant que variables locales et répondent aux exigences de RAII, mais la plupart des autres objets sont réservés dans le tas et sont soumis au garbage collection. Devenir.

Pointeurs intelligents

Un pointeur intelligent est un type de pointeur qui non seulement pointe vers une adresse mémoire, mais possède également des ** fonctions supplémentaires **. Le pointeur intelligent de Rust est celui qui alloue et libère la mémoire du tas ** "smart" ** comme String et Vec dans le type de bibliothèque standard. La clé pour distinguer les pointeurs intelligents dans Rust est de savoir s'ils implémentent ** "Deref traits" ** ou ** "Drop traits" **.

rust


{
    let a = String::from("hoge"); //Chaîne"hoge"Est réservé dans le tas
    let b = vec![1, 2, 3]; //Le vecteur est alloué en tas
    let c = Box::new(5); //L'entier de type i32 est alloué dans le tas
} //Variable a, b,c est détruit et en même temps la mémoire allouée dans le tas est libérée

Déplacer la sémantique

En gros, la sémantique de déplacement signifie que ** la propriété est transférée ** lors de l'affectation d'une valeur à une variable ou de la transmission d'une fonction en tant qu'argument.

rust


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

let s2 = s1; //Déplacer la sémantique: propriété`s1`De`s2`Se déplace vers

println!("{}", s2); // OK
println!("{}", s1); //Erreur de compilation: propriété`s2`Parce qu'il s'est déplacé vers`s1`Non accessible à

Dans le code ci-dessus, let s2 = s1; est la sémantique de déplacement. En effet, Rust limite toujours la propriété d'une valeur à un.

Monomorphisation

Les génériques de Rust sont étendus au moment de la compilation à des types spécifiques utilisés dans le programme, qui est appelé "monophase". Puisque "monophase" détermine la fonction à appeler au moment de la compilation, il s'agit d'une ** distribution statique **, et il n'y a pas de surcharge d'appel à l'exécution associée à l'abstraction.

Dans le C ++ affecté, la monophasicisation est appelée instanciation de modèle, spécialisation.

--Langue affectée - C++

Modèle de mémoire

Le modèle de mémoire a plusieurs implications, mais le modèle de mémoire dans ce contexte concerne la ** cohérence de l'accès à la mémoire partagée dans un environnement multithread **. En général, il existe des ** "opérations atomiques" ** qui peuvent être exécutées en toute sécurité à partir du multithreading, mais un modèle de mémoire est nécessaire pour les réaliser. Pour plus d'informations, reportez-vous à la documentation Rust ci-dessous.

Gestion de la mémoire basée sur la région

La gestion de la mémoire basée sur la région divise la mémoire en zones appelées «régions» et gère en outre la mémoire en association avec le système de types. Chez Rust, il semble être fortement impliqué dans la gestion à vie des références.

--Les références - Region-Based Memory Management in Cyclone - Cyclone: Memory Management Via Regions --Langue affectée - ML Kit, Cyclone

Classes de types, familles de types

«Classe de type» est un mot dérivé de Haskell, et la fonction correspondante dans Rust est ** trait **, qui est utilisé pour définir le comportement commun aux ** types **. Elle est similaire à l'interface Java, mais elle se caractérise par le fait que les traits peuvent être implémentés plus tard ** après la définition du type, pas lorsque le type est défini.

rust


trait Greeting { //Définition du trait
    fn greet(&self) -> String;
}

fn print_greet<T: Greeting>(person: T) { //Fonctions utilisant des limites de trait
    println!("{}!", person.greet());
}

struct Japanese { name: String, }        // `struct`Définition de type à l'aide de
struct American { name: String, age: u32,}

impl Greeting for Japanese { //Mise en œuvre des traits
    fn greet(&self) -> String { "Bonjour".to_string() }
}

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

impl Greeting for i32 { //Vous pouvez également implémenter des traits sur les types intégrés!
    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_La fonction de salutation peut être appelée pour différents types qui implémentent le message d'accueil(Polymorphe ad hoc)
    print_greet(person_a);
    print_greet(person_b);
    print_greet(123);
}

Expliquant dans le code ci-dessus, la fonction print_greet () est une fonction qui peut être appelée si le trait Greeting est implémenté. Et si le type japonais est déjà défini, vous pouvez l'appeler avec la fonction print_greet () en implémentant le trait Greeting (ʻimpl Greeting for Japanese). Ce qui est intéressant, c'est que les traits peuvent être mis à niveau vers des types intégrés comme ʻi32. La propriété d'une fonction qui peut augmenter les types qui peuvent être passés plus tard, comme cette fonction print_greet (), est appelée ** polymorphisme ad hoc **.

En gros, la "famille de types" est une fonction qui réalise une fonction de type qui reçoit un type et renvoie le type. Dans Rust, il existe une connexion avec ** "type associé" **. La définition et l'exemple d'utilisation sont tirés de ʻAdd` de la bibliothèque standard.

rust


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

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

impl Add for Point {
    type Output = Self; //Type connexe

    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 });

Les types associés sont déclarés dans le trait en utilisant type, comme dans le code ci-dessus. Vous pouvez faire quelque chose de similaire à Generics, mais c'est aussi utile. Si vous êtes intéressé, veuillez consulter les références suivantes pour des situations détaillées.

Canaux, concurrence

Les canaux sont des primitives pour la communication asynchrone. ** L'expéditeur et le destinataire peuvent transmettre des données de manière asynchrone via le canal **. Le code suivant est une partie de code tirée de Channel --Rust By Example version japonaise ( Commentaires remplacés par ceux d'origine).

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(); //Créer une chaîne
    let mut children = Vec::new();

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

        let child = thread::spawn(move || { //Créer un fil
            thread_tx.send(id).unwrap(); //Envoi de données via le canal
            println!("thread {} finished", id);
        });

        children.push(child);
    }

    let mut ids = Vec::with_capacity(NTHREADS as usize);
    for _ in 0..NTHREADS {
        ids.push(rx.recv()); //Recevoir les données envoyées par les threads enfants
    }

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

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

Le code ci-dessus est le code qui crée un canal, le transmet au thread enfant, envoie des données du thread enfant via le canal et les reçoit du thread parent.

--Langue affectée - Newsqueak, Alef, Limbo

Message passant, échec du thread

Je n'ai pas pu le savoir, alors je vais l'omettre.

--Langue affectée - Erlang

Reliure facultative

La liaison facultative est une fonctionnalité Swift qui, comme son nom l'indique, lie des variables et exécute des blocs de code lorsqu'une valeur facultative existe. La fonction correspondante de Rust est ʻif let, mais diverses correspondances de motifs sont disponibles, non limitées à ʻOption.

rust


let num = Some(10);

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

Macros hygiéniques

Une macro hygiénique est une macro qui garantit que le nom de variable introduit dans la macro et le nom de variable de l'appelant de macro ne sont pas en conflit. Voici un exemple de code pour une simple macro Rust.

rust


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

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

Les macros de Rust sont hygiéniques, donc même si la macro my_macro! Reçoit la variable ʻa, elle sera traitée différemment de la variable ʻa introduite dans le let interne. Cela peut entrer en conflit avec les macros C et Lisp, j'ai donc dû choisir délibérément des variables qui ne seraient pas en conflit.

--Langue affectée - Scheme

Les attributs

Les attributs sont principalement des ** informations supplémentaires (métadonnées) ** ajoutées à la déclaration. Un aspect courant dans Rust est l'attribut # [test], qui marque un test unitaire.

rust


#[test] //attribut(Marquage de la fonction de test)
fn test_hoge() {
    // test code
}

#[allow(dead_code)] //attribut(Avertissement suppression des fonctions inutilisées)
fn foo() {}

#[derive(Debug, Clone, Copy, Default, Eq, Hash, Ord, PartialOrd, PartialEq)] //attribut(Implémentation automatique des traits)
struct Num(i32);

Syntaxe de fermeture

Vous pouvez voir que c'est similaire si vous comparez la notation de bloc de Ruby et la notation de fermeture de Rust.

ruby


ia = [1,2,3]

ia.each {|e| puts e } #Bloc de rubis(`each`Arguments de)

rust


let ia = [1, 2, 3];

ia.iter().for_each(|e| println!("{}", e)); //Fermeture antirouille(`for_each`Arguments de)

--Langue affectée - Ruby

Visualisation de l'impact

J'ai essayé de visualiser les langages qui ont influencé Rust. Les langues sont grossièrement disposées dans le sens des aiguilles d'une montre dans l'ordre chronologique. Les couleurs sont grossièrement classées par FP, POO, calcul parallèle et autres.

En regardant cette figure, nous pouvons voir comment les langues de différents paradigmes sont influencées de manière équilibrée.

Résumé

J'ai grossièrement catégorisé les langues qui ont influencé Rust dans un tableau et j'ai essayé de les visualiser davantage. Nous avons également donné une introduction approximative aux caractéristiques individuelles qui l'ont influencé. La description de Influences de la référence Rust originale est la suivante.

À première vue, Rust semble être doté de nombreuses fonctionnalités avancées, mais beaucoup d'entre elles sont ** basées sur des résultats de recherche et des langages éprouvés **. Même les systèmes de propriété et les contrôleurs d'emprunt dont on parle souvent comme une fonctionnalité de Rust sont fortement influencés par des langages tels que C ++, ML Kit et Cyclone. Et quand j'ai examiné les influences individuelles, il était intéressant de voir le flux que ** Rust a pu réaliser une pause par rapport au GC complet qui ne pouvait pas être réalisée avec ML Kit ou Cyclone **.

Je ne pensais pas que tant de fonctionnalités venaient d'autres langues avant de les rechercher. Les âges et les paradigmes des langues touchées sont également diversifiés, et en recherchant Rust, je suis tombé dans le sentiment de ** apprendre l'histoire de l'évolution des langues **. Et je pense que Rust nous a montré le processus pour surmonter avec succès les lacunes de chaque langue et intégrer les bons points.

Il est vrai que si vous êtes influencé par une telle langue, on dit que la courbe d'apprentissage est raide pour les débutants, mais si vous y réfléchissez à l'inverse, c'est une compilation de différentes langues affectées. En écrivant ceci, j'ai pensé qu'on pourrait dire que c'est un langage très enrichissant et rentable ** qui peut être appris à.

Nous espérons que cet article aidera ceux qui s'intéressent à Rust.

Les références

Recommended Posts

Les langues qui ont influencé Rust
Ceci et cela de JDK
Une tentative de "puzzles mathématiques qui entraînent davantage le cerveau de Rust".
"Puzzles mathématiques qui entraînent davantage le cerveau du programme" _Q39 (code: Ruby) -> Rust
"Puzzles mathématiques qui entraînent davantage le cerveau du programme" _pp.018-020 (code: Ruby) -> Rust
"Puzzles mathématiques qui entraînent davantage le cerveau du programme" _Q02 (code: Ruby) -> Rust
"Des énigmes mathématiques qui entraînent davantage le cerveau du programme" _Q17 (code: Ruby) -> Rust
"Des énigmes mathématiques qui entraînent davantage le cerveau du programme" _Q01 (code: Ruby) -> Rust