Go (langage)

Go
Logo.

Date de première version
Paradigme Langage compilé, programmation concurrente, impérative et structurée
Auteur Robert Griesemer (en)
Rob Pike
Ken Thompson
Dernière version 1.23.3 ()[1]Voir et modifier les données sur Wikidata
Typage Fort, statique, structurel
Influencé par C
Oberon-2 (en)
Limbo
Active Oberon (en)
Communicating sequential processes
Pascal
Oberon
Smalltalk
Newsqueak (en)
Modula-2
Alef
APL
BCPL
Modula (en)
OccamVoir et modifier les données sur Wikidata
Système d'exploitation Windows, GNU/Linux, Mac OS X, FreeBSD, OpenBSD, DragonflyBSD, Solaris, Plan 9[2]
Licence Licence BSD[3], breveté[4]
Site web go.devVoir et modifier les données sur Wikidata
Extension de fichier goVoir et modifier les données sur Wikidata

Go est un langage de programmation compilé et concurrent inspiré de C et Pascal. Il a été développé par Google[5] à partir d’un concept initial de Robert Griesemer (en), Rob Pike et Ken Thompson.

Logo de Google Go
Mascotte de Google Go

Go veut faciliter et accélérer la programmation à grande échelle : en raison de sa simplicité, il est donc concevable de l’utiliser aussi bien pour écrire des applications, des scripts ou de grands systèmes. Cette simplicité est nécessaire aussi pour assurer la maintenance et l’évolution des programmes sur plusieurs générations de développeurs.

Cette dernière s'exprime à la fois dans le langage lui-même, avec une syntaxe simple, qui minimise le nombre d'abstractions et de fonctionnalités ; mais également dans la pile des technologies utilisées. Go dispose d'une bibliothèque standard particulièrement complète — qui s'enrichit à chaque mise à jour — et permet de réaliser de nombreuses tâches, sans nécessiter l'ajout de bibliothèques externes.

S’il vise aussi la rapidité d’exécution, indispensable à la programmation système, il considère le multithreading comme le moyen le plus robuste d’assurer sur les processeurs actuels cette rapidité[6] tout en rendant la maintenance facile par séparation de tâches simples exécutées indépendamment afin d’éviter de créer des « usines à gaz ». Cette conception permet également le fonctionnement sans réécriture sur des architectures multi-cœurs en exploitant immédiatement l’augmentation de puissance correspondante.

« Hello, world »

Voici un exemple d'un programme Hello world typique écrit en Go :

package main

import "fmt"

func main() {
	fmt.Println("Hello, world")
}

Caractéristiques

Le langage Go a été créé pour la programmation système et a depuis été étendu aux applications, ce qui constitue la même cible que le C et surtout le C++. Il s'agit d'un langage impératif et concurrent.[réf. nécessaire] Sa vitesse de compilation (due à la simplicité de sa syntaxe) le fait parfois utiliser comme langage de script.[réf. nécessaire]

Concurrence

Go intègre directement, comme Java, les traitements de code en concurrence. Le mot clé go permet à un appel de fonction de s'exécuter en concurrence avec la goroutine courante[7]. Une goroutine, ainsi nommée par analogie lointaine avec les coroutines, est un fil d'exécution supervisé par l'ordonnanceur inclus dans le runtime[8]. Le programme prendra alors avantage de la topologie de l'ordinateur pour exécuter au mieux les goroutines, pas forcément dans un nouveau thread, mais il est aussi possible qu'un groupe de goroutines soit multiplexé sur un groupe de threads[9].

Pour appeler une fonction f, on écrit f(). Pour l'appeler en tant que goroutine, on écrit simplement go f(), ce qui est très semblable au call f task; de PL/I, langage gérant également le multitâche depuis 1970.

Les goroutines communiquent entre elles par passage de messages, en envoyant ou en recevant des messages sur des canaux[10].

Ces messages synchronisent les goroutines entre elles conformément au modèle CSP, considéré par les auteurs comme plus intuitif que le modèle multi-threadé (avec synchronisation par sémaphores comportant des verrous, notion introduite elle aussi par Dijkstra).

Faux-amis

Dans l'état actuel du langage (2018)

Le langage contient des aspects de Pascal et de C, mais on s'expose à beaucoup d'erreurs si on oublie momentanément qu'on n'est ni en Pascal ni en C. Ainsi a:=b alloue une variable a en lui affectant la valeur et le type de b, mais si la variable a déjà été allouée il faudra se contenter d'écrire a=b. À ne pas confondre avec a==b (égalité de valeurs). L'expression derrière un if n'a pas besoin de parenthèses, mais l'expression à exécuter en cas de réussite du test devra être entre accolades. Dans son état actuel, le compilateur ne tolère pas qu'une variable déclarée ou un module importé ne soit pas utilisé, ce qui encourage certes de bonnes pratiques, mais rend fort pénibles les tâtonnements de mise au point des programmes.

Collections d'objets

Go connaît les types scalaires (entiers int ou int64, flottants float, chaînes string), les tableaux indexés par des entiers à partir de 0, les maps qui sont des collections d'objets indexés par des clés (nommés dictionnaires, hashes ou tableaux associatifs dans d'autres langages) et des slices qui sont une généralisation dynamique des tableaux.

Il accède aisément aux fichiers en lecture comme en écriture, que ce soit en mode ligne, ou caractère, ou bien en absorbant le fichier complet, par les packages os et io.

Go a un système de type statique, fortement typé, structurel et sûr, fondé sur l'inférence de types avec la possibilité d'utiliser un typage explicite.

Pour donner un exemple, écrire s := "Camélia", qui déclare, alloue et initialise s, est possible et n'oblige pas à écrire var s string = "Camélia", qui reste cependant accepté.

La compatibilité des types composés est fondée sur les propriétés plutôt que sur le nom. C'est-à-dire que deux types composés seront équivalents si leurs propriétés sont équivalentes : même nom pour la propriété et équivalence de type. C'est le typage structurel.

Cela a pour conséquence que le langage n'est pas objet au sens classique (soit avec classes, soit avec prototype), cependant les concepteurs du langage ont fait un choix plus original pour un langage statique. Il est possible de définir des interfaces portant des méthodes décrivant le comportement d'un objet (il est aussi facilement possible de mélanger plusieurs interfaces en une seule)[11]. Les fonctions Go peuvent déclarer accepter un argument de cette interface. Un objet déclarant toutes les méthodes de cette interface, avec la même signature, peut être passé en argument de cette méthode. La vérification du type est effectuée statiquement par le compilateur.

Le fait que Go ne soit pas objet au sens classique fait que Go n'a pas d'héritage de type et pas de sous-classage. Ceci permet de contourner les problèmes posés par ces systèmes tels l'héritage multiple dans les langages qui le permettent (en C++ par exemple), ou l'héritage simple (en Java par exemple). Grâce à l'équivalence de types fondée sur les propriétés, Go n'a pas besoin d'héritage de type. Le sous-classage est émulé par l'« embarquement de type ». Ceci permet de mélanger facilement deux bases de code conçues indépendamment, sans qu'elles aient besoin de partager des types communs.

La visibilité des structures, attributs, variables, constantes, méthodes, types de haut niveau et des fonctions hors de leur paquetage de déclaration est définie par la casse du premier caractère de leurs identificateurs.

Runes

Go travaille en Unicode aussi bien pour son code source que pour son traitement des chaînes. Sa littérature abandonne toutefois l'expression code points pour l'abréviation de runes. Des procédures permettent de transformer des représentations de caractère en runes (toute rune occupant une taille fixe[12]) et transformer par des procédures standards une suite de caractères Unicode en runes d'un tableau (une rune par élément), ainsi que l'inverse, sans avoir à jongler avec les représentations de longueur variables ni s'occuper de savoir si la machine est little-endian ou big-endian. La portabilité est donc assurée.

Parler de runes évite les ambiguïtés qui seraient présentes avec caractère ou octet[13]. Pour explorer une chaîne, soit on utilise directement des fonctions de chaîne, soit on la parcourt de rune en rune.

Les mises en capitales de caractères nationaux (par exemple "è"⇒ "È") se font simplement par unicode.ToUpper(r rune) rune. Les runes permettent de spécifier en tout caractère Unicode, thaï, chinois, grec, y compris du langage APL- et également toute émoticône s'y trouvant.

Générique

Le manque de support de la programmation générique dans les versions initiales de Go a généré beaucoup de critiques[14]. Les concepteurs ont exprimé qu'ils n'étaient pas opposés à la programmation générique en précisant que certaines fonctions intégrées du langage étaient génériques, mais qu'elles étaient vues comme des exceptions. Rob Pike appelait cet état de fait "une faiblesse" pouvant être changée à un moment ou un autre[15]. L'équipe de Google avait réalisé au moins une version du compilateur pour expérimenter un dialecte intégrant les génériques dans Go, mais ne l'avait jamais publié[16].

En août 2018, les contributeurs principaux de Go ont publié un premier jet d'une proposition de document de conception[17] incluant la programmation générique et le traitement des erreurs par exception. Ils ont ensuite demandé aux utilisateurs de leur soumettre leurs avis sur ces possibles évolutions[18]. L'évolution sur le traitement des erreurs fût abandonnée[19].

En juin 2020, une nouvelle proposition de document de conception[20] fut publiée avec les ajustements et la syntaxe nécessaire à faire dans Go pour déclarer les fonctions et les types génériques. Cette nouvelle proposition fut accompagnée d'un outil permettant de convertir la nouvelle syntaxe intégrant les génériques en une version compilable par les versions existantes de Go, ceci afin de permettre aux utilisateurs de tester ces nouvelles évolutions[21].

Finalement, les génériques furent intégrés dans la version 1.18 de Go le 15 mars 2022[22].

Divers

Dans Go, la gestion de la mémoire est assurée par un ramasse-miettes[11].

Il n'y a pas de surcharge de méthodes ou d'arithmétique des pointeurs. Enfin, il n'y a pas d'assertions ou d'exceptions. Pour remplacer ces deux derniers, Go fournit les mots clés defer, panic et recover[23] qui donnent des mécanismes similaires aux systèmes de gestion des exceptions de langages tels que C++ et Java (mots clés try, catch, finally et throw).

Go peut s'interfacer avec des bibliothèques en C/C++, des développeurs tiers ayant déjà développé des bindings pour SDL et MySQL.

Go définit un format de code standard (au niveau des indentations, et de la présentation des structures de contrôle) et fournit un outil pour l'appliquer (go fmt).

Go propose également un système de documentation à partir du code et un framework de test.

L'unité de compilation de go est le package qui est représenté dans l'implémentation standard par un répertoire et les fichiers directement contenus dans ce répertoire.

L'import d'un package se fait par son chemin d'importation et peut préciser soit une bibliothèque standard, soit également des packages tiers installés dans des dépôts de sources distants (actuellement supporté : dépôt sous svn, git, mercurial et bazaar).

Extensions expérimentales

Bien que Go soit au départ destiné à produire des applications système robustes et non des programmes destinés à des utilisateurs, des liens avec OpenGL sont développés à titre expérimental[24].

Bibliothèques

Liste des bibliothèques de Go (début).

Comme le C, Go exige d'indiquer quelles bibliothèques on utilisera. À la différence du C, il considère la compilation en erreur si cette bibliothèque n'est pas utilisée. Le compilateur Go ne contient en effet pas de messages d'avertissements par choix des concepteurs : « Il n'est pas indispensable de signaler ce qu'il ne serait pas indispensable de corriger ».

Principales bibliothèques :

  • fmt fournit 19 fonctions de mise en forme des entrées-sorties dont Println, Print et Printf ;
  • io fournit des procédures d'entrées-sorties elles-mêmes, dont ReadFull, Seek, WriteString...
  • math fournit un ensemble de fonctions mathématiques, telles que Min, Max, Sqrt, Log...
  • os interface le système, par exemple pour ouvrir et fermer des fichiers, dont OpenFile, ReadAt...
  • strings fournit une cinquantaine de fonctions traitant des chaînes de caractères ;
  • time fournit des outils de mesure des temps d'exécution, dont time.Now et time.Since().

Les bibliothèques sont tantôt indépendantes, tantôt dépendantes. Il existe par ailleurs plusieurs façons de faire des choses similaires. Par exemple pour lire un fichier, on pourra utiliser ioutil.ReadAll, file.Read() ou bufio.NewScanner().

On obtient la liste des bibliothèques depuis la ligne de commande par go list all.

Exemple de programme

Go admet l'appel récursif des fonctions, ce qui peut parfois les rendre plus lisibles, sans perte de vitesse excessive :

Calcul récursif de la suite de Fibonacci par un programme go chronométré. La récursivité rend ici uniquement le programme plus lisible, car on arriverait au même résultat avec une simple boucle. L'instruction commençant par if s[n] !=0 réutilise directement tout résultat déjà calculé. Si on la supprime, la lenteur du programme devient calamiteuse.
package main

import "fmt"
import "time"

var s [61]int

func fib(n int) int {
        if n < 3 {
                return 1
        }
        if s[n] != 0 {
                return s[n]
        }
        s[n] = fib(n-1) + fib(n-2)
        return s[n]

}

func main() {
        var i int
        t0 := time.Now()
        for i = 1; i <= 60; i++ {
                fmt.Printf("fib(%d) = %-15d\t", i, fib(i))
        }
        println()
        println("Durée :", time.Since(t0).Seconds())
}

Ce programme est formaté à la manière standard du go par l'utilitaire gofmt avec les options -s (simplifier le code si possible) et -w (écrire le code modifié in-situ). Cet utilitaire aligne les niveaux de profondeur du programme, facilitant la maintenance en particulier si plusieurs développeurs doivent maintenir le même code. On remarquera qu'il n'aligne pas verticalement les accolades ouvrantes et fermantes, la plupart des éditeurs actuels (2018) se chargeant de signaler visuellement les correspondances en mode édition.

Implémentations

Go possède deux implémentations : la première utilise gc, le compilateur Go ; la seconde utilise gccgo, « frontend » GCC écrit en C++.

Le compilateur gc était écrit en C en utilisant yacc et GNU Bison pour l’analyse syntaxique jusqu’à la version 1.4[25], mais est écrit en Go lui-même pour les versions suivantes (depuis 1.5) avec un processus de bootstrap.

Un projet secondaire de compilateur basé sur LLVM existe[25].

Possibilités évoluées, syntaxe désuète, lisibilité par rapport à C

Les choix syntaxiques de Go ne font pas l'unanimité. Si le langage se réclame simpliste dans son écriture, certains lui reprochent des partis pris trop impactants et dogmatiques à cet égard[26] en qualifiant sa syntaxe de confuse[27],[28] et son compilateur d'une rigidité discutable[29], citant entre autres :

  • la portée des variables contrôlée par la présence ou non d'une majuscule ;
  • l'absence d'opérateur ternaire ;
  • le manque de sucre syntaxique ;
  • le refus de compilation s'il existe des variables non utilisées ;
  • la gestion des erreurs et notamment les conditions répétitives inhérentes à leur vérification[30] ;
  • l’incohérence de certaines APIs du langage comme les channels[31].

Le langage s'est fait reprocher d'avoir « un moteur de Ferrari dans une carrosserie de Ford T »[32].

En revanche, Go propose de simplifier la syntaxe du C, intentionnellement conservée en C++ pour assurer la compatibilité ascendante, en supprimant par exemple les parenthèses syntaxiquement inutiles derrière le if et le for, en proposant un fall-through par défaut dans un switch, etc. Ces modifications peuvent fournir des programmes plus lisibles[33].

Projets utilisant Go

Liste d’applications libres notables écrites en Go[34] :

  • Docker - Création, déploiement et exécution d'application dans des conteneurs.
  • Go Ethereum : Une des trois implémentations originales (avec C ++ et Python) du protocole Ethereum.
  • Gogs - Forge multiplateforme fondée sur Git.
  • Grafana - Outil de monitoring, d'analyse de métriques et de création de tableaux de bord.
  • Kubernetes - Système de gestion d'applications conteneurisées.
  • Hugo : générateur de sites web statiques.
  • Caddy (en) : Serveur de pages Web.
  • Syncthing : application de synchronisation de fichiers pair à pair open source disponible pour Windows, Mac, Linux, Android, Solaris, Darwin et BSD.
  • Terraform : Environnement logiciel d'« infrastructure as code » publié en open-source par la société HashiCorp.
  • Vaultt : Outil de gestion de secrets développé par la société Hashicorp
  • Consul : Outil de service discovery et de service mesh développé par la société Hashicorp
  • Rclone : Utilitaire de synchronisation de fichiers dans le Cloud ou en montage de système de fichiers.
  • Helm : Gestionnaire de paquets utilisé pour déployer des applications sur Kubernetes
  • Prometheus : Outil de supervision informatique
  • Packer : Outil de création d'images pour machines virtuelles édité par la société Hashicorp
  • Traefik : Reverse proxy et répartiteur de charge open-source
  • Minio : Une solution de stockage objet open source compatible avec AWS S3

Notes et références

  1. a et b « Release History »
  2. (en) « Go on Plan9 ».
  3. (en) « Text file LICENSE » (consulté le ).
  4. (en) « Additional IP Rights Grant », sur golang (consulté le ).
  5. (en) https://techcrunch.com/2009/11/10/google-go-language/.
  6. (en) [vidéo] « Rob Pike sur la programmation concurrente », sur YouTube.
  7. Cette notion avait été introduite dans PL/I en 1970 en faisant suivre un appel de sous-programme du mot TASK.
  8. « Frequently Asked Questions (FAQ) - The Go Programming Language », sur golang.org (consulté le )
  9. « Frequently Asked Questions (FAQ) - The Go Programming Language », sur golang.org (consulté le )
  10. Voir Message Passing Interface.
  11. a et b (en) Rob Pike, « Go at Google » (consulté le ).
  12. La version 6.3 d'Unicode comprenant 210 000 possibilités de caractères, plus de 21 bits sont nécessaires et on peut supposer qu'une rune occupera la même place qu'un int32. Le standard initial de Go était qu'une rune occupe autant de place qu'un int16. Aucun code source n'a dû pour autant être modifié.
  13. Chaînes en langage Go : les runes
  14. Alice Merrick, « Go Developer Survey 2020 Results », sur Go Programming Language, (consulté le )
  15. Rob Pike, « Arrays, slices (and strings): The mechanics of 'append' », sur The Go Blog, (consulté le )
  16. « E2E: Erik Meijer and Robert Griesemer », sur Channel 9, Microsoft,
  17. « Go 2 Draft Designs » (consulté le )
  18. « The Go Blog: Go 2 Draft Designs »,
  19. « Proposal: A built-in Go error check function, "try" », sur Go repository on GitHub (consulté le )
  20. « Type Parameters — Draft Design », sur go.googlesource.com
  21. « Generics in Go », sur bitfieldconsulting.com
  22. « Go 1.18 is released! », sur Go Programming Language, (consulté le )
  23. « Defer, Panic, and Recover », sur blog.golang.org, .
  24. (en) https://github.com/go-gl.
  25. a et b (en) « FAQ - The Go Programming Language », golang.org, 6 octobre 2011.
  26. (en) « Ten Reasons Why I Don't Like Golang », sur www.teamten.com, (consulté le )
  27. (en) « My Thoughts on Go », sur blog.goodstuff.im, (consulté le )
  28. (en) Cliff Berg, « The "go" language is a mess », sur Value-Driven IT, (consulté le )
  29. (en) « Go is a poorly designed language », sur aletheia.icu, (consulté le )
  30. (en) Jesse Hallett, « Changes I would make to Go », sur sitr.us, (consulté le )
  31. (en) « Go channels are bad and you should feel bad », sur www.jtolio.com, (consulté le )
  32. Langage Go : une Ford T avec un moteur Ferrari
  33. « A C++ developer looks at Go (the programming language), Part 1: Simple Features », sur www.murrayc.com (consulté le )
  34. (en) « Go Projects »

Annexes

Sur les autres projets Wikimedia :

Liens externes