Selon moi, Go est un mauvais langage pour plusieurs raisons que je vais décrire ici.
Programmation générique
En effet, une des premières critiques de Go est la programmation générique. Les développeurs du langage étaient contre les génériques au début ne voulant pas compliquer le langage de manière significatif. La programmation générique est possible dans les langages avec un typage dynamique comme Python mais ce dernier rajoute un overlay au runtime (pour savoir qu'elle type on manipule) - ce papier de recherche montre l'implémentation d'un typage dynamique dans OCaml et parle de se qu'on nomme le polymorphisme ad-hoc.
Mais ce genre de système n'a pas était retenu par les développeurs de Go (et d'OCaml d'ailleurs). Cependant, avec un système de type bien pensé, le polymorphisme est tout autant possible (OCaml le prouve tout les jours avec ses foncteurs). On pourrait se rabattre aux templates ce qui rajouterait par la même occasion la méta-programmation mais il me semble que ce dernier est le principal facteur du temps de compilation du C++ qui reste le lance de guerre (à défaut d'en avoir d'autre) de Go.
Alors qu'on se le dise bien, le système de type de Go reste médiocre par rapport à ses compères tel que OCaml et Haskell. Et on voit donc l'apparition de interface{}
. C'est typiquement ce que je critique à pas mal de langage informatique qui est celui de suivre son intuition pour répondre à une problématique à l'aide de rustines - ce qui n'est jamais bon pour un langage. C'est un peu l'équivalent d'Obj.magic
en OCaml qui permet de by-passer tout le système de type pour avoir un void*
ce qui reste en contradiction avec le système de typage fort puisque désormais, interface{}
rajoute un type Top
(cf. TAPL) ou Object
(cf. Java) - on pourrait dire la même chose d'OCaml avec Obj.magic
mais:
Obj.magic is not part of the OCaml language
Et l'utilisation de interface{}
semble être la norme pour les utilisateurs de Go.
Surcharge
C'est un problème similaire à OCaml avec l'opérateur +.
et les flottants. Cependant, les développeurs essayent de trouver une solution tout en restant conforme avec le système de type existant. Les développeurs de Go ont raison de faire attention sur ce point qui peut être la source de pas mal de bug si c'est couplé avec un système de typage faible et, développant avec OCaml tout les jours, cela resterait occasionnellement efficace AMHA.
Du côté d'OCaml, la solution reste la redéfinition des fonctions pour le type attendu. Et nous pouvons faire ceci pour l'instant:
| module Float = struct let ( + ) = ( +. ) end
Float.(4.0 + 2.0)
|
Je ne pense pas qu'il y est d'équivalent en Go. Cependant, cela reste une vraie question. Avons nous réellement besoin de la surcharge ? En C++, dès que le code-base devient important, la surcharge peut être un vrai problème puisque, même si le compilateur trouve la bonne fonction/le bonne opérateur, ce n'est pas forcément le cas du développeur.
Le pointeur Nil
L'histoire nous a montré qu'il fallait minimiser l'existence du NULL
comme c'est le cas en C++ ou dans des langages plus haut niveau comme Python, Ruby, OCaml, Haskell, etc. Je pense que Go pouvait se permettre de réduire son existence à néant même si il devait y avoir un overlay sur les fonctions disponibles dans la LibC (OCaml y est parvenu) - le Nil
casse tout autant le système de type fort aussi.
Immutabilité
Il faut parfois offrir les outils nécessaire afin d'empêcher le programmeur à faire des erreurs. L'immutabilité est une solution permettant de mieux localiser les erreurs dans le code et permet d'offrir des assertions intéressantes et ainsi confiner les effets de bords au mieux possible. Swift et JavaScript plus récemment ont offert cette possibilité, elle est bien plus fondamentale dans les langages tel qu'OCaml ou plus extrême dans Haskell.
Je considère qu'un langage qui se veut être safe devrait pouvoir offrir ce genre d'outil. Rob Pike disait à ce sujet que le développeur pouvait transgresser ce genre de contrainte si vous avez le choix de faire quelque chose de plus efficace et si vous savez ce que vous faites (on peut imaginer qu'il fait mention au const_cast
de C++). Je pense surtout qu'il y a un problème de cible ici, Go a voulu s'imposer à l'époque comme un remplaçant du C++ et du C ce qui a échoué vu l'état de la communauté et de ses objectifs qui sont différents des développeurs C et C++ - cela ne veut pas dire que Go n'a pas sa place dans le paysage des langages informatiques, mais qu'il s'est trompé de place.
Système embarqué
La première critique et la plus fondamentale est l'utilisation d'un garbage collector. La programmation dans des systèmes embarquées consiste à de la programmation dans des environnements hostiles dans lequel la latence est facteur à prendre réellement au sérieux. L'allocation dans le tas (qui est quasi le cas en Go) doit être utilisé à bon escient. C'est le cas par exemple dans Rust qui offre les outils nécessaires pour limiter l'utilisation du tas d'allocation.
Go n'a pas fait ce choix et forcément, c'est un langage qui ne correspond pas à cette classe de problème qui demande une gestion en temps réel (il ne devrait y avoir aucune latence, mais il y a forcément une dans un garbage collector et qui est difficilement prédictible). La communauté de Go en fait donc un autre usage et la question d'efficacité du code vu plus haut reste alors moins importante dans des contextes tel que l'implémentation d'un serveur HTTP - cela ne veut pas dire que Go est lent, bien au contraire, mais certaines problématiques refusent qu'il y est une latence non prédictible, et c'est dans ces contextes qu'on utilise le C où la gestion de la mémoire est bien plus fine.
La caractéristique unsafe des langages tel que le C est parfois aussi une qualité dans ce genre de système où faire des choses dangereuses comme ceci:
| *(uint8_t*)0x1234 = 0xFF;
|
devrait être possible. C'est une programmation bas niveau qui dépends énormément de l'architecture sur laquelle on travaille mais qui change complétement la donne par rapport à Go. Il n'est pas possible de faire ce genre de chose en OCaml ou en Haskell, et c'est normal, ils n'ont pas cette ambition. Go semblerait avoir cette ambition, de faire de la programmation bas niveau, sans pour autant en donner les outils. Rust permet de faire ce genre de code avec unsafe{}
ce qui permet entre autre une isolation.
Conclusion
Pour moi, Go est un langage fragmenté par le choix entre safety et efficiency et qui n'a pas réussi. On a une communauté qui vise des logiciels dont le safety prime et des développeurs qui martèlent l'efficacité du code Go sans y arriver. Malheureusement, cette fragmentation apparaît dans les débats internes du langage où la communauté demande des outils plus haut niveau et où les développeurs refusent en disant que ça complexifierait trop le langage. Mais parfois ils cèdent et ça laisse des horreurs comme interface{}
. Bref, si l'équipe de développement de Go ne veut pas s'avouer vaincu face à ses objectifs qui divergent par rapport à ceux de la communauté, je préfère passer mon chemin.