Si
la puissance de calcul des ordinateurs a été constamment améliorée ces dernières années, les logiciels et les architectures se sont également complexifiés : les premiers interagissent souvent entre eux (parfois via un réseau), et requièrent de plus en plus d'autres logiciels pour fonctionner ; les secondes proposent désormais
plusieurs unités traitantes, comme par exemple plusieurs processeurs, qui permettent théoriquement une augmentation des performances, mais se révèlent en pratique être
un cauchemar pour le programmeur.
Certains ingénieurs de la société Google ont ainsi constaté que le développement logiciel est de plus en plus complexe à organiser, et qu'il prend de plus en plus de temps, tout en étant de moins en moins sûr. Ces ingénieurs ont alors émis l'idée que les outils utilisés jusqu'à présent n'étaient plus adaptés aux défis actuels. Go, le nouveau langage qu'ils ont conçu pour répondre aux défis récents, se veut simple, efficace tout en restant adapté au traitement de problèmes complexes. Ce projet de recherche, qui date de 2007, vient d'être révélé au grand jour mardi 10 novembre dernier.
Une perspective nouvelle
Un pari audacieux
Le but de Go est de mieux tirer parti des processeurs multi-cœurs, d'être plus fiable que ses prédécesseurs, et, surtout, d'être moins complexe, tant pour le programmeur que pour le compilateur. Tels sont les paris pris par Rob Pike, Robert Griesemer et Ken Thompson lorsqu'ils commencent à travailler en 2007 sur un nouveau langage, héritier du C, mais modernisé et adapté aux nouveaux enjeux du développement logiciel. Selon eux, un certain nombre de mauvaises habitudes ont été prises par les programmeurs, et les
langages les plus utilisés (C, puis C++, Java ou C#) y sont pour quelque chose. Pire, les personnes qui cherchent à échapper à ces langages se mettent à utiliser des
langages dynamiques comme PHP, JavaScript, Perl ou Python, qui, s'ils présentent de bonnes idées, sont en revanche conçus et implémentés de façon désastreuse.
Les trois ingénieurs décident alors de se pencher sur la création d'un langage aussi efficace que les
langages compilés habituels, mais aussi agréable à utiliser qu'un langage dynamique comme Python. Ce nouveau langage doit être
- aussi fiable que possible : à la compilation, le programme doit être garanti sans bugs liés aux types ou à une mauvaise gestion de la mémoire. C'est à dire que le programme compilé ne doit pas risquer de planter parce que le programmeur a accidentellement écrit une opération dénuée de sens, comme additionner un entier et une chaîne de caractères, ou tenter d'accéder à une position en mémoire qui n'existe pas. Ces bugs sont parmi les plus répandus, et doivent être découverts par le compilateur si le programmeur ne les aperçoit pas tout seul.
- équipé d'un ramasse-miettes : en C, une mauvaise gestion de la mémoire peut provoquer des plantages ou une consommation inutile. Pour éviter ceci, elle devra donc être confiée à un ramasse-miettes, c'est à dire à un programme qui s'occupe des allocations à la place du programmeur (de façon transparente).
- adapté à la gestion du parallélisme : on veut pouvoir écrire des programmes qui fonctionnent de façon efficace par dessus le réseau, ou en tirant parti de plusieurs processeurs en même temps. Cependant, cela requiert un certain nombre de précautions : que se passe-t-il si plusieurs sous-programmes tentent de modifier en même temps une même donnée ? Ce problème, très actuel, a entraîné la création de nouvelles technologies au cours des dernières années, sous forme de bibliothèques (par exemple Grand Central Dispatch) ou de langages spécialisés (Erlang).
- performant : la rapidité d'exécution des programmes écrits avec ce langage doit être comparable à celle du C. Bien que cet objectif dépende davantage moins du langage lui-même que de son implémentation (c'est à dire son compilateur, etc.), certains choix doivent être faits pour l'atteindre. Notamment, le langage doit être statique, c'est à dire que les types des données manipulées doivent être connus à la compilation, et pas à l'exécution comme dans le cas de langages dynamiques.
- simple, tant dans sa conception qu'à l'utilisation : les langages modernes sont de plus en plus complexes (car ils possèdent de plus en plus de caractéristiques), et manquent toutefois de fonctionnalités parfois jugées essentielles. Ils disposent alors de bibliothèques pour pallier ces manques, et ces dernières complexifient encore le rôle du développeur. Un langage moderne doit donc se débarasser de cette fâcheuse habitude. De plus, la syntaxe devra être simple, concise, mais lisible et régulière.
- capable de compiler le plus rapidement possible.
Une équipe de choc
Glenda, la mascotte de Plan 9,
dessinée par la femme de Rob Pike.
Bien que de petite taille, l'équipe chargée de concevoir ce nouveau langage promet d'être efficace. Elle se compose en effet de gens ayant joué des rôles importants dans le développement de l'informatique. Thompson, tout d'abord, est l'un des créateurs du système Unix, ainsi que du langage B (ancêtre du C). Pike est également l'un des ingénieurs ayant conçu Unix au sein des laboratoires Bell, et a de plus travaillé sur
Plan 9, un successeur expérimental d'Unix visant à corriger certains défauts du système. Avec Thompson, il a également conçu le système d'encodage
UTF-8. Robert Griesemer, enfin, a travaillé pour Sun Microsystems sur
Java HotSpot, un moyen d'optimiser efficacement le langage Java, ainsi que sur
V8, le moteur JavaScript du navigateur Google Chrome (connu pour ses performances).
Durant l'année 2008, le langage Go est pratiquement conçu, et une première implémentation est mise en oeuvre. D'autres ingénieurs rejoignent le projet, et l'équipe se concentre alors sur l'optimisation du langage et de sa compilation. Deux compilateurs sont développés :
6g, nommé ainsi d'après la tradition des
compilateurs du système Plan 9, ainsi que gccgo, basé sur le compilateur GCC. Les deux implémentations sont pour l'instant maintenues, la première en raison d'une plus grande rapidité, la seconde pour son efficacité accrue. Pour l'heure, il n'existe pas de compilateur tournant sous Windows : l'équipe déplore une
trop faible quantité de programmeurs, mais espère que l'ouverture totale du langage et de ses implémentations (qui sont libres) attirera des contributeurs.
Le langage Go
Un descendant du C revu et amélioré.
À l'instar du C, Go est un
langage impératif : un programme se compose comme une suite d'instructions qui ont un ordre. Go est clairement un héritier du C, dont il reprend une partie de la syntaxe. Cependant, celle-ci a été revue afin d'être à la fois plus claire et plus concise (moins de choses à écrire). Voici le code d'un programme helloworld.go retournant "Hello, world ; salut les Zéros" comme résultat :
Code : Python | package main
import fmt "fmt" // Contient des fonctions de formatage
func main() {
fmt.Printf("Hello, world ; salut les Zéros\n");
}
|
Un certain nombre de différences sautent immédiatement aux yeux. Vous pouvez immédiatement voir que ce programme fait partie du paquet nommé "main", qu'il utilise le paquet "fmt" pour le formatage. Au sein de la fonction main (qui sera appelée au lancement du programme), vous retrouvez le
printf du C, mais actualisé, puisqu'il fait partie du paquet "fmt". Les paquets servent à organiser les dépendances, en les rendant moins chaotiques qu'en C. Notez que les chaînes de caractères de Go sont prévues pour gérer l'encodage UTF-8.
Gordon, la mascotte de Go (même auteur)
Naturellement, il y a bien plus à dire sur la parenté entre Go et C. Parmi les points qui ont été revus au sein de Go, Rob Pike présente notamment dans sa conférence la déclaration de variables. En Go, on écrit par exemple
var a, b *int
plutôt que
int *a,*b;
pour déclarer des pointeurs vers des entiers. Cette nouvelle syntaxe est jugée plus intuitive. D'ailleurs, lors de l'utilisation de pointeurs, la mémoire sera allouée automatiquement par le ramasse-miettes - le programmeur n'a plus à s'en soucier.
D'autres ambiguités ont été levées : le
piège des opérateurs ++ et -- a été levé, puisqu'ils sont désormais considérés comme des instructions et plus comme faisant partie d'une expression. Le but est d'enlever les erreurs et indéterminations en empêchant d'écrire des choses comme
p[i] = q[++i]. Les pointeurs du C sont également présents, mais leur utilisation a été restreinte, puisque notamment
l'arithmétique de pointeurs n'est plus possible : les créateurs du langage soutiennent que ce type d'opérations n'est plus nécessaire aujourd'hui, et qu'il complique l'utilisation du ramasse-miettes.
Le site du langage présente notamment
une page dédiée aux différences avec le langage C++, dont certains points valent aussi pour le C.
Un système de types simple mais moderne.
Go veut privilégier la concision, en permettant au programmeur de ne rien écrire d'inutile. Dans la conférence de présentation du langage, Rob Pike donne notamment l'exemple suivant
foo.Foo *myFoo = new foo.Foo(foo.FOO_INIT) de code que Go veut éviter. Ainsi, si un programmeur Go doit écrire
var t0 *T = new(T);
, ce qui est parfaitement valide mais inutilement redondant, il peut l'abréger en
t1 := new(T);
. L'opérateur := dénote une affectation initiale, comme on le ferait
dans le langage Io. Cependant, ça n'est pas parce que le type de
t1 n'est pas donné explicitement qu'il est inconnu à la compilation : le compilateur déduit automatiquement ce type via un mécanisme que l'on appelle "
inférence de types", comme dans les langages
Haskell ou
OCaml.
Pour atteindre la concision et l'efficacité, Go présente un système de types proche de celui du langage Python, appelé "
duck typing". Ce nom original représente un principe simple : si un objet a un bec de canard, et qu'il cancanne comme un canard, alors on peut l'utiliser comme un canard. Plus sérieusement, en Python,
on ne se préoccupe pas du type exact des objets, mais des opérations supportées. Cela permet par exemple d'utiliser la même fonction
len
sur des listes ou des chaînes de caractères pour en mesurer la longueur. Cela permet aussi au programmeur d'implémenter l'opération correspondant à la fonction
len
sur ses propres objets pour que ceux-ci puissent également être utilisés avec la fonction.
Cependant, Python est flexible au détriment de la sécurité : si vous utilisez une fonction
cancanner sur un objet qui ne cancanne pas, l'erreur ne sera détectée que pendant l'exécution de votre programme, et pas pendant la compilation. Cela pose problème car pour vérifier votre programme, vous devez alors le tester dans de multiples situations - ce qui ne garantit même pas son fonctionnement à 100 %. Go va donc plus loin, en proposant de vérifier à la compilation les opérations supportées par vos objets. Ceci est réalisé par un mécanisme
d'interfaces, comme dans les langages Java ou Objective-C (où ils sont appelés "protocoles").
Par exemple, la fonction
Printf sait afficher les objets répondant à l'interface
Stringer, c'est à dire ceux équipés d'une méthode
String. Voici un exemple de définition d'une interface, puis d'un nouveau type respectant cette interface :
Code : Python | type Stringer interface {
String() string
}
type testType struct { a int; b string }
func (t *testType) String() string { // Ceci est bien une méthode du type testType
return fmt.Sprint(t.a) + " " + t.b // Le + peut servir à concaténer des chaînes
}
|
Ici, testType est une structure comportant un entier a et une chaîne b, et on définit comment afficher les objets de type testType. Lorsque
Printf voudra afficher un de ces objets, il appellera la méthode
String et affichera le résultat. Cependant, notez bien que rien dans ce code ne laisse supposer la présence de
classes comme dans la majorité des langages objets : les concepteurs de Go jugent en effet que ce concept n'est pas nécessaire à la programmation orientée objet, ou à la programmation tout court. Notamment, ils jugent l'introduction de hiérarchie entre les types (par exemple par héritage) comme étant artificielle et inadaptée. Le système d'interfaces permet quant à lui un polymorphisme jugé simple et naturel.
Enfin, les
types de base proposés par Go sont grossièrement les mêmes types de base que ceux de Python : le programmeur aura rarement à se préoccuper de la différence entre
int
et
long
, ou
float
et
double
, mais pourra se contenter de distinguer
int
et
float
(entier ou décimal). De plus, des types plus ou moins élaborés comme les chaînes, les tableaux, mais aussi les "slices" (sous-tableaux utilisant des références) et les "maps" (tableaux associatifs) sont fournis par défaut. Enfin, les fonctions peuvent être très naturellement manipulées, notamment passées en argument à d'autres fonctions.
Les outils du parallélisme
Go est un langage dit "
concurrent", c'est à dire conçu pour faire tourner plusieurs tâches plus ou moins indépentantes en même temps, et pour les faire communiquer de façon naturelle. Pour cela, Go implémente le paradigme
CSP, conçu par Charles Hoare en 1978. Ce paradigme conçoit les processus comme tournant indépendamment les uns des autres, avec pour seul moyen de communication des messages qu'ils s'envoient les uns aux autres (à condition de connaître l'adresse du destinataire).
Cette façon de faire était déjà implémentée par certains langages, notamment
Erlang, qui a connu un certain succès dans le domaine de l'écriture de serveurs (notamment
ejabberd) supportant facilement des milliers de connexions simultanées, mais dont la puissance de calcul était en revanche très limitée. Go pourrait donc proposer une alternative plus adaptée aux calculs lourds dans ce domaine.
De même qu'en Erlang, les processus utilisés pour la programmation concurrente sont des "processus légers", dont l'exécution peut être lancée facilement de façon à ce que des milliers de processus s'exécutent simultanément. On les appelle "goroutines" en Go, afin de les distinguer d'éventuels processus systèmes, plus lourds et dédiés à une utilisaiton différente. Même si ce côté n'a pour l'instant été que peu évoqué pour Go, ils peuvent alors être répartis sur plusieurs cœurs, voire sur plusieurs ordinateurs, sans que cela perturbe le déroulement du programme.
Dans l'exemple suivant, la fonction
processus va être lancée dans deux nouveaux processus, via l'instruction go :
Code : Python | func server(i int) {
for { // La boucle for seule équivaut au while du C
print(i);
sys.sleep(10)
}
}
go server(1);
go server(2);
|
La communication entre processus se fait simplement à l'aide de l'opérateur
<-. Des exemples peuvent être trouvés dans
la documentation du langage.
Quel avenir pour le langage ?
Malgré des caractéristiques alléchantes, il est encore trop tôt pour dire si Go réussira à percer. Les langages qu'il veut détrôner semblent multiples (C++ ? Java ? Python ?) et sont tous bien plus matures que lui. D'ailleurs, Go n'est pour l'instant guère plus qu'un intéressant projet de langage, encore susceptible d'évoluer. De plus, des langages comparables existaient déjà ces dernières années. Par exemple, le
langage D prétendait lui aussi être plus moderne que C++, et être capable de remplacer ce langage. Pour l'instant, rien de tel ne s'est produit. De même, OCaml, développé par l'INRIA, est fiable, performant, et en certains points comparables à Go - mais il n'a pas vraiment non plus connu de succès dans le milieu industriel.
Ainsi, le futur de Go est incertain. Même son nom est susceptible d'être modifié : il existe en effet déjà un langage concurrent nommé
Go!, ce qui ressemble beaucoup trop au langage de Google pour son auteur. Cependant, pour certains, le milieu de la programmation a bien toléré pendant des années que des langages s'appellent C, C++ et C#...
Pour les passionnés, Go représente en tout cas au moins un jouet intéressant, et éventuellement un langage adapté pour l'apprentissage de la programmation concurrente. Google n'a pas annoncé pour l'instant de projet majeur écrit en Go, même si le serveur du
site officiel du langage, Golang.org est écrit en Go. Il sera donc également intéressant de voir dans quelle mesure Go est adopté par ses propres géniteurs, et notamment d'étudier s'il remplace Python, très utilisé par Google en interne.
Quelques liens :