Uploaded avatar of iHiD

C'est le Mechanical March !

@iHiD
il y a Plus de 3 ans
Vidéo

Bienvenue dans notre deuxième mois à thème : Mechanical March. Ce mois-ci, on se concentre sur les langages système, ceux qui se compilent en code machine.

C'est en partie un article, en partie la transcription de la vidéo Mechanical March. Je vais te faire une brève introduction du mois, puis on regardera un peu les langages système, leur évolution au fil de l'histoire, les avantages et les inconvénients de compiler en code machine, et un rapide coup d'œil à chacun des langages mis en avant. Erik est de nouveau à mes côtés, et c'est lui qui assurera l'essentiel de la discussion dans la seconde partie. Mais je vais commencer par quelques informations pratiques.

Alors, d'abord, les langages mis en avant ce mois-ci. Ce sont C, C++, D, Go, Nim, Rust, V et Zig. Pour obtenir le badge Mechanical March, tu dois terminer cinq exercices dans l'un de ces langages. Notre parcours Go propose l'un des meilleurs programmes d'Exercism, alors je te recommande vraiment de l'essayer. On est aussi de grands fans de Nim, ici, à Exercism, car c'est un langage relativement simple pour débuter et très facile à écrire, donc je te recommande vraiment de l'essayer aussi.

On a aussi cinq exercices mis en avant à essayer :

  • Liste chaînée ou liste chaînée simple (selon le langage) : allocation et libération de mémoire, pointeurs
  • Poignée de main secrète : opérations bit à bit
  • Pangramme : boucles for, strings et caractères
  • Crible : tableaux, boucles for
  • Recherche binaire : tableaux, boucles

Il y a aussi un nouveau badge, que j'ai annoncé il y a quelques jours dans ma vidéo de mise à jour : il s'obtient en terminant les cinq exercices mis en avant dans les langages du thème. Donc pour obtenir ce badge, il te faudra terminer tous ces exercices dans un langage système à un moment de l'année.

On a aussi plein de choses sympas qui mijotent : des interviews avec quelques personnes de l'équipe principale de Go, et on l'espère, de Rust et de quelques autres langages aussi. On aura aussi plein de diffusions en direct tout au long du mois. Et des goodies Mechanical March qui arrivent bientôt aussi !

Alors, penchons-nous un peu plus sur le côté technique.

À quoi servent ces langages ?

On les utilise un peu partout chez Exercism. Notre CLI est écrite en Go, notre outil interne de gestion des parcours, configlet, est écrit en Nim, et la bibliothèque essentielle qui compte les lignes de code de tes solutions est écrite en Rust. Erik, pourquoi a-t-on choisi ces langages pour ces outils ?

CLI :

  • Je crois qu'on a utilisé Go parce que c'est ce que Katrina connaissait le mieux.
  • Go est parfait pour ces outils en ligne de commande de taille modeste.
  • Le code Go est relativement simple, ce qui facilite les contributions.
  • Les binaires Go sont faciles à déployer, car ils ne nécessitent aucun environnement d'exécution.
  • Go gère bien la compilation croisée.

Nim

  • Nim a la plupart des mêmes avantages que Go.
  • On n'avait pas beaucoup de personnes connaissant Go capables de maintenir la CLI, alors on est passés à Nim.

Où d'autre s'attendrait-on à trouver ces langages ?

  • Partout où la performance compte (pilotes, jeux, systèmes d'exploitation, systèmes de compilation et compilateurs)
  • Partout où les ressources sont limitées (par exemple les logiciels embarqués)
  • Tout ce qui doit être très portable, c'est-à-dire tourner sur de nombreuses plateformes différentes

Qu'est-ce que le code machine ?

Comme je le disais plus tôt, les langages de Mechanical March se distinguent par le fait qu'ils compilent en code machine. Peux-tu expliquer un peu ce qu'est le code machine et, par contraste, ce qu'est le bytecode ?

  • Le code machine est du code qui peut s'exécuter directement sur la machine.
  • Le bytecode, en revanche, a besoin d'un autre code pour l'interpréter ou le compiler en code machine. Il nécessite donc une étape intermédiaire avant de pouvoir être exécuté.

Quels sont les avantages et les inconvénients du code machine par rapport au bytecode ?

Avantages :

  • Démarrage plus rapide (pas d'étape de compilation JIT)
  • Empreinte mémoire plus faible (aucun environnement d'exécution chargé, pas de bytecode en mémoire, idéal pour les systèmes embarqués)
  • La machine cible n'a pas besoin d'un environnement d'exécution installé (important pour garder les conteneurs Docker petits)

Inconvénients :

  • Non portable. Le bytecode est portable, mais le code machine compilé est spécifique à une plateforme.
  • Impossible de faire des optimisations avancées comme l'optimisation guidée par profil (déterminer la meilleure façon de compiler le (byte)code après l'avoir exécuté un moment).

Note : des approches hybrides sont possibles, où le langage compile en bytecode puis utilise un autre outil pour compiler ce bytecode en code machine.

L'évolution de la programmation système

Bien. Regardons un peu l'évolution de certains des langages de ce mois-ci. Commençons par le commencement, avec C, et voyons comment C++ en a évolué. Parle-nous un peu de ces langages.

Le C est un langage très bas niveau. On a l'impression d'être juste un peu au-dessus du code machine. Cela le rend très puissant et très optimisable, mais aussi assez propice aux bugs (par exemple les exceptions de pointeur nul et les débordements de tampon). La gestion de la mémoire est entièrement manuelle, et donc à la charge du programmeur, ce qui peut entraîner des bugs ou des fuites de mémoire. Le C++ est comme le C, mais avec la prise en charge de la programmation orientée objet. Il reste assez bas niveau et nécessite une gestion manuelle de la mémoire. Le C comme le C++ te permettent d'écrire de l'assembleur en ligne (ASM) !

Et les langages système plus récents ? Comment ont-ils évolué ?

Tous les langages de programmation système modernes prennent en charge la gestion automatique de la mémoire, que ce soit par comptage de références, par un ramasse-miettes ou par un autre mécanisme.

Les premiers langages de programmation système gèrent tous les pointeurs nuls (ce que Tony Hoare a appelé son « erreur à un milliard de dollars »). Ces pointeurs sont tristement célèbres pour provoquer des erreurs à l'exécution et des vulnérabilités. Beaucoup de langages modernes se passent de null, ou du moins exigent un effort particulier pour l'utiliser.

Autre changement : on est passé de valeurs mutables par défaut à des valeurs immuables par défaut. Par exemple, Rust et Vlang ont tous deux des valeurs immuables par défaut, et il faut opter explicitement pour la mutabilité.

Tous les langages plus récents prennent en charge l'interopérabilité avec C (ou C++), car énormément de code a été écrit dans ces langages.

Autre point intéressant : certains des langages plus récents ne compilent pas directement en code machine, mais passent par d'autres outils pour le faire. Par exemple, Rust et Zig utilisent LLVM, tandis que Nim permet d'utiliser une grande variété de compilateurs. C'est ce qu'on appelle la transpilation.

Et les macros et la métaprogrammation ?

Il y a une séparation intéressante en ce qui concerne les macros et la métaprogrammation. Les macros en C/C++ sont puissantes, mais ont une réputation un peu douteuse d'être difficiles à utiliser. Rust, Nim et D offrent tous une métaprogrammation puissante, mais d'une manière bien plus agréable. À l'inverse, VLang et Zig mentionnent tous deux explicitement l'absence de macros comme une fonctionnalité de leur langage, et Go adopte une approche différente avec go generate.

Les langages système ont la réputation d'être assez bas niveau. Est-ce encore justifié ?

Les langages plus récents travaillent tous à des niveaux d'abstraction plus élevés que C/C++. Par exemple, Rust, D et Nim permettent aussi une façon très fonctionnelle d'écrire du code. Nim et D ont même le concept de fonctions « pures ».

Introduction aux langages du mois

Ce serait donc une bonne idée de les passer en revue un par un. Tous ces langages se ressemblent : ils sont tous fortement et statiquement typés. Mais regardons ce qui les distingue. On commence par C ?

C

  • Développé par Dennis Ritchie
  • L'un des plus anciens, et probablement l'un des plus utilisés au monde
  • D'énormes quantités de logiciels sont écrites en C, comme Unix et Linux
  • Très influent (pense aux « langages de type C »)
  • Gestion manuelle de la mémoire
  • Très performant (proche du « métal »)
  • Tourne partout
  • Parfait pour les systèmes embarqués
  • Un langage assez petit

C++

  • Développé par Bjarne Stroustrup
  • Successeur du C, mais avec l'orientation objet en plus (C with classes)
  • A contribué à populariser la programmation orientée objet
  • Plus de fonctionnalités de haut niveau que le C
  • Prend en charge la programmation générique via les templates
  • Ajoute la prise en charge des modules via les namespaces
  • De nombreux moteurs de jeux sont écrits en C++, ainsi qu'une grande partie de Windows
  • Gestion manuelle de la mémoire
  • Toujours en évolution, avec de nouvelles fonctionnalités ajoutées régulièrement (une grosse spécification)

D

  • Développé par Walter Bright, rejoint plus tard par Andrei Alexandrescu
  • Considéré à l'origine comme un C++ repensé (tirant les leçons de ses « erreurs »), il s'inspire de nombreux autres langages
  • Multi-paradigme : programmation impérative, orientée objet et fonctionnelle
  • Interopérabilité facile avec C/C++
  • Uniform Function Call Syntax
  • Compile Time Function Evaluation (par exemple la génération d'une machine à états de regex à la compilation)
  • Prend en charge la programmation fonctionnelle et les fonctions « pures »
  • Nombreuses fonctionnalités de sûreté
  • Sûreté mémoire via @safe
  • Contrats (préconditions et postconditions, invariants)
  • Fonctions pures
  • Accent mis sur les tests unitaires, avec les tests à côté du code source testé (il a fallu une exception sur le site d'Exercism :))
  • Unicode

Rust

  • Développé par Graydon Hoare, employé de Mozilla Research, puis officiellement adopté par Mozilla et aujourd'hui rattaché à la Rust Foundation
  • Multi-paradigme : prend en charge la POO (mais avec des partis pris, par exemple pas d'héritage), la programmation impérative et la programmation fonctionnelle (types Option/Result, filtrage par motif)
  • De nombreux nouveaux outils écrits en Rust (par exemple SWC, mais aussi Gleam, et deuxième langage pris en charge dans le noyau Linux ; on en dépend pour le compteur de lignes de code)
  • Accent sur la fiabilité et la performance
  • Basé sur LLVM
  • Langage le plus apprécié dans l'enquête StackOverflow depuis 7 ans
  • Rapide, en partie grâce à un cœur et une bibliothèque standard minimaux
  • Sûr, à la fois en mémoire et en concurrence, grâce à la propriété et aux durées de vie, immuable par défaut
  • Système de types puissant, qui attrape de nombreux bugs à la compilation (surtout liés à la mémoire). Le compilateur produit des messages d'erreur vraiment utiles
  • Tout est inclus : compilateur, outil de compilation, outil de formatage, gestionnaire de paquets, intégrations IDE
  • Une documentation superbe (il existe même un énorme document sur le fonctionnement du compilateur)
  • Portable : compile en un binaire unique et statique et ne nécessite pas d'environnement d'exécution installé
  • Interopérabilité facile avec le code C
  • Abstractions à coût nul
  • Concurrence sans peur
  • Macros

Nim

  • Développé par Andreas Rumpf (nommé à l'origine Nimrod)
  • Syntaxe inspirée de Python
  • Multi-paradigme
  • Utilisé chez Exercism dans configlet
  • Excellentes performances : itérateurs sans surcoût, préférence pour l'allocation sur la pile des types basés sur des valeurs
  • Système de types moderne et expressif : inférence de types, tuples, génériques, types somme, async/await
  • Ramasse-miettes, mais avec prise en charge d'une gestion déterministe de la mémoire (plusieurs options de gestion de la mémoire)
  • Exécution de code à la compilation
  • Syntaxe d'appel uniforme
  • Macros : possibilité d'étendre facilement le langage
  • Système d'effets : encodage des effets de bord dans le système de types

Go

  • Développé par Robert Griesemer, Rob Pike et Ken Thompson chez Google
  • Principalement impératif et procédural, avec une approche proche de l'orienté objet (mais sans héritage)
  • Utilisé dans de grands projets comme Docker et Kubernetes. Également excellent pour les backends et les CLI (par exemple esbuild)
  • Vise à être assez simple pour tenir dans une seule tête (peu de syntaxe)
  • Sûreté mémoire grâce au ramasse-miettes
  • Rapide : compilation rapide, tests rapides et exécution rapide. Prise en charge intégrée de l'écriture de benchmarks !
  • Avec de forts partis pris : beaucoup d'efforts pour influencer le style du code Go, avec peu de syntaxe, le formatage du code via go fmt, des outils pour vérifier que le code est idiomatique, des erreurs là où d'autres langages se contentent d'avertissements (par exemple les variables inutilisées), et une documentation qui liste les idiomes Go à suivre
  • Système de types léger qui rend Go très flexible (prend en charge l'inférence de types)
  • Portable : compile en un binaire unique et statique et ne nécessite pas d'environnement d'exécution installé. Compilation croisée facile. Concurrence via les goroutines (légères) et communication via les canaux
  • Typage structurel via les interfaces (proche du duck typing, mais vérifié statiquement)
  • Gestion des erreurs : le langage encourage à vérifier et à traiter les erreurs

VLang

  • Développé par Alexander Medvednikov et Delyan Angelov
  • Inspiré de Go :
  • Même stratégie du « une seule façon de faire les choses »
  • Même stratégie de « syntaxe minimale »
  • Coroutines
  • Différences avec Go :
  • Pas de nil/null, mais utilise un type résultat
  • Immuable par défaut
  • Types somme (fonctionnel)
  • Interpolation de chaînes
  • Environnement d'exécution et binaires plus petits
  • Filtrage par motif
  • Travaille à rendre le ramasse-miettes optionnel (autofree)
  • Interopérabilité C sans surcoût
  • Génération de documentation à partir du code
  • Compilateur rapide et économe en mémoire

Zig

  • Développé par Andrew Kelley
  • Assez peu de syntaxe (un fichier de grammaire PEG de 500 lignes)
  • Exécution de code et réflexion à la compilation
  • Vise à être « évident », sans flux de contrôle, allocation ou macros/métaprogrammation cachés
  • Allocation manuelle de la mémoire
  • Prend en charge différents allocateurs
  • Les fonctions de la bibliothèque standard qui allouent prennent un paramètre d'allocateur
  • Le framework de test peut détecter les fuites de mémoire
  • Sûreté :
  • Les erreurs sont des valeurs et doivent être traitées
  • Pas de null, utilise un type optionnel
  • Les tests peuvent être écrits dans le même fichier que le code source (comme en D)
  • Utilise LLVM comme backend
  • Peut compiler du code C/C++
  • Interopérabilité C facile
  • Compilation croisée facile

Conclusion

On va s'arrêter là, car j'imagine que tout le monde a le cerveau bien rempli à ce stade.

J'espère que cette introduction aux langages mis en avant ce mois-ci t'a été utile et agréable. J'espère que tu t'amuseras beaucoup à explorer ces langages. Erik et moi serions ravis de savoir lesquels tu choisis et ce que tu en penses, alors n'hésite pas à poster dans les commentaires ou sur le forum !

Merci d'avoir regardé !

1er Mar 2023 · Tu l'as trouvé utile ?