1 points par GN⁺ 2024-02-08 | 1 commentaires | Partager sur WhatsApp
  • La variable de boucle for est désormais recréée à chaque itération, ce qui évite les bugs de partage involontaire, et range peut aussi s’appliquer aux entiers
  • La compatibilité respecte toujours la promesse de compatibilité Go 1, et l’on s’attend à ce que presque tous les programmes Go se compilent et s’exécutent comme auparavant
  • Dans un workspace, il est possible d’utiliser le répertoire vendor généré avec go work vendor, et si un vendor de workspace est présent, -mod=vendor devient la valeur par défaut
  • Fin de la prise en charge de go get hors module dans le mode legacy GOPATH avec GO111MODULE=off, tandis que les autres commandes de build comme go build et go test continuent de fonctionner pour les programmes legacy GOPATH
  • Le runtime place désormais les métadonnées GC basées sur les types à proximité des objets du tas, ce qui améliore les performances CPU des programmes Go de 1 à 3 % et réduit d’environ 1 % la surcharge mémoire de nombreux programmes
  • L’alignement d’adresse de certains objets peut passer de 16 octets ou plus à un alignement sur 8 octets, et le code assembleur supposant un alignement supérieur doit être modifié ; la solution de contournement temporaire GOEXPERIMENT=noallocheaders peut être utilisée
  • Les builds avec PGO peuvent dévirtualiser davantage d’appels, et sur un ensemble représentatif de programmes Go, l’activation de PGO améliore les performances à l’exécution de 2 à 14 %
  • Ajout à la bibliothèque standard du premier package v2, math/rand/v2, sans héritage de Read, avec un générateur global toujours initialisé aléatoirement, les sources ChaCha8 et PCG, ainsi que la fonction générique N
  • Les motifs de routage de net/http.ServeMux prennent désormais en charge les méthodes et les jokers, avec de petites ruptures de compatibilité dans les motifs contenant {} et dans le traitement des chemins échappés ; il est possible de restaurer l’ancien comportement avec GODEBUG=httpmuxgo121=1
  • Dans crypto/tls, la version minimale par défaut côté serveur passe à TLS 1.2 lorsqu’elle n’est pas spécifiée, et les suites de chiffrement sans ECDHE ne sont plus proposées par défaut lors des handshakes antérieurs à TLS 1.3
  • Le traceur d’exécution a été entièrement remanié : il utilise l’horloge de l’OS sur la plupart des plateformes, prend en charge des traces segmentées compatibles avec un traitement en streaming, et fournit la durée complète des syscalls ainsi que des informations sur le thread OS ayant exécuté une goroutine
  • L’interface web de l’outil trace a été mise à jour dans le cadre du nouveau support du traceur, et prend en charge, pour les traces de programmes compilés avec Go 1.22 ou plus, une vue orientée thread ainsi que l’affichage de la durée complète des syscalls
  • vet ajuste ses avertissements sur les fermetures de boucle pour refléter la sémantique des variables de boucle de Go 1.22, et ajoute des avertissements pour append sans valeur, time.Since non différé dans defer, ainsi que pour les incohérences clé/valeur dans log/slog
  • Le bootstrap nécessite Go 1.20 au moins dans son dernier point release, et il est prévu que Go 1.24 exige au minimum le dernier point release de Go 1.22
  • Sur darwin/amd64, la toolchain Go génère désormais des PIE par défaut, et Go 1.22 est la dernière version à fonctionner sur macOS 10.15 Catalina ; Go 1.23 exigera macOS 11 Big Sur ou plus récent
  • Le type Alias et la fonction Unalias ont été ajoutés à go/types, et avec gotypesalias=1, les clients doivent prendre en charge le type Alias
  • slices.Delete, DeleteFunc, Compact, CompactFunc et Replace remettent à zéro les éléments de la plage réduite, et Insert déclenche désormais toujours un panic si i est hors limites
  • Les fonctions de lecture de répertoires sous Windows lisent désormais les entrées par lots pour réduire le nombre d’appels système, avec des gains de performances pouvant atteindre 30 %

1 commentaires

 
GN⁺ 2024-02-08
Avis sur Hacker News
  • Ces trois dernières années, j’ai surtout utilisé TypeScript, puis je me suis récemment mis à Go. Au début, j’étais un peu inquiet à cause du manque de fonctions sur les tableaux et d’un système de types moins flexible.
    Mais en reconfigurant un projet TypeScript pour une équipe de 4 à 5 personnes, je me suis rendu compte qu’on passait énormément de temps sur le linting, le choix d’une bibliothèque de routage serveur, le choix du serveur, les standards de code, la gestion d’erreurs de base, l’imposition d’erreurs personnalisées ou d’un type Result pour éviter l’enfer des try/catch imbriqués, les tests et les mocks, la configuration de Prisma, etc.
    J’aime TypeScript, mais utiliser un langage avec une excellente bibliothèque standard, une vraie sûreté de typage et des standards de code intégrés est vraiment confortable. Go a aussi ses quirks, mais il est plutôt correct, et le fait que même le routage arrive dans la bibliothèque standard enlève encore un point auquel penser, ce qui est appréciable. Maintenant, j’attends juste les fonctions map/filter/find pour les slices.

    • Dans une newsletter de CTO, ils ont récemment comparé les versions de TypeScript et de Go, en prenant Go 1.22 comme exemple.
      À chaque release, TypeScript devient de plus en plus complexe pour les power users, tandis que Go ajoute des éléments qui manquaient, comme range sur les entiers, afin de rendre son utilisation plus simple.
      C’est un peu comme les suites de jeux qui ajoutent sans cesse des paramètres et des mécaniques, jusqu’à avoir besoin d’un reset pour les nouveaux joueurs. Comme c’est difficile à faire pour un langage de programmation, c’est bien que Go en soit conscient.
    • J’ai un ressenti similaire. Ce qui m’a fait rester sur Go, c’est sa chaîne d’outils de build étonnamment unifiée, et ce qu’on peut faire avec go:embed et go:generate m’impressionne chaque fois que je le vois dans d’autres projets.
      Les paquets golang.org/x sont aussi appréciables, avec presque toutes les implémentations liées aux RFC Internet prêtes à l’emploi.
    • En attendant l’arrivée de map/filter/find pour les slices, on peut utiliser la bibliothèque lo, inspirée de lodash.
      C’est mon couteau suisse favori dans les projets Go : https://github.com/samber/lo
    • Le fait qu’il n’y ait pas énormément de fonctionnalités réduit au contraire les petites décisions, et, contrairement à ce qu’on pourrait croire, c’est assez productif.
    • J’ai travaillé un moment chez un client qui utilisait TypeScript, et j’ai fini par appeler ça du « Tricks Driven Development ».
      À chaque fois que je lisais la doc pour faire quelque chose, quelqu’un finissait vite par m’indiquer un truc utilisable qui n’était pas dans la documentation.
  • Si les notes de version officielles vous semblent un peu austères, j’en ai fait une version interactive : https://antonz.org/go-1-22

    • Le problème du partage des variables de boucle for ne m’a pas paru évident au départ, mais voir le résultat inattendu en l’exécutant moi-même m’a aidé.
    • Les exemples Compact et Replace prêtent à confusion ; je ne sais pas si c’est bien correct.
  • J’utilise Go depuis plus de 9 ans, et ces 4 dernières années j’ai beaucoup utilisé Dart à cause de Flutter. À mon avis, les deux langages ont des attitudes diamétralement opposées face à la complexité. Dart essaie d’ajouter toutes les fonctionnalités possibles, Go fait l’inverse.
    Premièrement, dans Dart, il y a beaucoup trop de façons d’initialiser des valeurs dans une classe, ce qui fait perdre beaucoup de temps. Selon les préfixes final/const/static/late, l’endroit d’initialisation change : constructeur, factory, initState() d’un StatefulWidget Flutter, etc. Après un refactoring, il faut souvent retravailler largement l’initialisation. Les getters qui ressemblent à des variables ajoutent aussi de la confusion dans une nouvelle codebase.
    Deuxièmement, Dart offre beaucoup de fonctionnalités faciles à condenser en une ligne — maps, streams, etc. — ce qui donne envie de produire rapidement du code en une seule ligne difficile à comprendre. Mais au débogage, ou quand un développeur junior tombe sur des hacks comme firstWhereOrNull et des paramètres optionnels comme orElse: () => null, c’est très déroutant. La null safety obscure ou les erreurs de type bloquent aussi souvent, et les remplacer par une simple boucle for à la Go procure un vrai soulagement.

    • Petit conseil Dart : mieux vaut ne pas utiliser initState pour ce qui n’est pas directement lié à l’UI. Par exemple, je l’éviterais sauf pour quelque chose comme définir l’indication d’un champ texte.
      Sur la plupart des pages Flutter, soit il y a très peu à faire, soit le parent crée un objet MyPageController, l’initialise comme il le souhaite, puis la page enfant se comporte en fonction de ce contrôleur. Par exemple, dans une page de liste, quand l’utilisateur veut modifier un élément, on crée un contrôleur, on y place l’élément et on l’envoie à la page enfant ; au retour, le parent peut regarder l’élément du contrôleur, ses variables ou ses callbacks pour décider des changements d’UI.
      Cela permet de contrôler finement les interactions entre widgets sans toucher à des choses comme InheritedWidget. Bien sûr, c’est mieux de l’utiliser avec une bibliothèque de gestion d’état, mais en pratique on s’en passe souvent.
    • Je suis d’accord pour dire que la simplicité de Go est son plus grand avantage. Pour range, c’est pareil : au final, le nombre de caractères tapés est similaire à une compréhension du type forEach(f), mais comme c’est réparti sur trois lignes, ça paraît simplement plus long ; quand on l’écrit explicitement, il n’y a rien de spécial.
      En revanche, pour l’initialisation, j’aimerais qu’il existe en Go une manière idiomatique d’initialiser les champs d’une structure à des valeurs spécifiques. L’absence de constructeur ne me dérange pas, mais il m’arrive de vouloir qu’un bool soit initialisé à true par défaut.
    • initState est une surcharge de StatefulWidget, ne diffère pas des événements de cycle de vie d’autres frameworks, et relève d’un concept Flutter ; ce n’est donc pas quelque chose qu’on confond naturellement avec un constructeur.
      On peut hésiter entre un constructeur normal et une factory, mais il est difficile de dire que Go est supérieur à cause de initState.
    • Il faut optimiser la capacité de spécialistes coûteux à communiquer de manière concise entre eux. Retirer des outils syntaxiques puissants des mains des débutants ne fait que les empêcher de développer leur expertise.
  • Quand io.Copy copie depuis TCPConn vers UnixConn, il utilise si possible l’appel système Linux splice(2), et passe par la nouvelle méthode TCPConn.WriteTo.
    Grâce à une mise à niveau d’interface, cela augmente encore une fois de manière transparente les E/S sans copie. L’impact que le package io obtient avec ce pattern est vraiment énorme.

    • La bibliothèque standard de Go contient beaucoup d’ajustements de ce genre. Ce n’est pas forcément une mauvaise chose, mais ces traitements spéciaux ne passent pas vraiment à l’échelle, donc l’intégration avec des bibliothèques non standard devient moins satisfaisante.
      Cela dit, ça reste dans les limites de ce à quoi on peut s’attendre.
  • Les modèles de routage améliorés sont une bonne évolution, maintenant que l’avenir de Gorilla Mux est incertain
    Cela dit, ce changement casse la rétrocompatibilité de façon limitée : certains points sont évidents, comme le comportement différent des modèles contenant { et }, d’autres le sont moins, comme le traitement des chemins échappés. C’est contrôlé par le champ httpmuxgo121 de GODEBUG, et définir httpmuxgo121=1 permet de revenir à l’ancien comportement
    Je me demande toutefois pourquoi cela n’entre pas en conflit avec la promesse de compatibilité Go 1, selon laquelle « les programmes écrits pour la spécification Go 1 continueront à compiler et à s’exécuter correctement, sans modification, pendant toute la durée de vie de cette spécification »

    • La façon de contourner la promesse de compatibilité Go 1 consiste à lier ce genre de changement non rétrocompatible à la valeur de la directive go dans go.mod
      En 1.22 ou plus, on obtient le nouveau comportement de la bibliothèque ; sinon, l’ancien. Reste à voir à quel point cela fonctionnera bien en pratique
    • En général, l’équipe Go parcourt GitHub et des programmes open source avant de casser quoi que ce soit, pour voir comment les gens l’utilisent. Ils ont probablement estimé que les cas où { et } sont utilisés dans les chemins des handlers HTTP étaient très rares
      Ils fournissent aussi un moyen de désactiver le nouveau comportement, donc ils ne forcent pas à modifier le code, même s’il faut définir une nouvelle variable d’environnement
      Le changement de sémantique des boucles for dans cette version est un exemple similaire : c’est, de fait, un changement cassant. Mais tous les programmes Go continuent de compiler et de s’exécuter ; certains comportements changent juste un peu. Go a choisi une approche pragmatique, et je pense que c’est l’une des raisons de son succès
    • Je vois ça comme une erreur rare de la part de Go. S’ils doivent utiliser go.mod pour casser les choses selon la version, ils devraient au moins les casser d’une meilleure manière
      Quand on passe go.mod à 1.22, je préférerais largement que le code échoue à la compilation plutôt que de devoir traquer des problèmes subtils à l’exécution
    • Sinon, on perd un slogan court et fort. Petit ou non, c’est un changement incompatible
  • Dans Go 1.22, chaque itération d’une boucle crée désormais une nouvelle variable
    Cela avait déjà été discuté ici : https://news.ycombinator.com/item?id=33160236 - Go: Redefining For Loop Variable Semantics (2022)

  • C’est peut-être vieux jeu, mais je n’aime pas l’ajout de range sur les fonctions. Le gain de confort ne me semble pas suffisant pour justifier la complexité ajoutée au langage, et le style fonctionnel paraît en décalage avec le style explicite, impératif, verbeux mais peu chargé en fonctionnalités de Go. Je pense que c’était l’un des grands atouts de Go
    Pour la même raison, je pense que range sur les entiers va dans la mauvaise direction. L’ensemble de fonctionnalités réduit de Go et sa faible charge cognitive ont toujours été des forces majeures et un élément différenciant

    • Pour l’instant, cette fonctionnalité est optionnelle et expérimentale, et doit être activée explicitement. Si elle devait être activée par défaut, j’aimerais qu’il existe un mécanisme de désactivation pour inciter les développeurs à ne pas l’utiliser lorsqu’elle n’est pas couramment employée
      L’un des grands problèmes du développement logiciel est d’empêcher les développeurs d’ajouter de la complexité ou de vouloir montrer leur talent. Les développeurs en début de carrière, en particulier, ont tendance à surcomplexifier les solutions plutôt que de résoudre le problème de façon directe, moins élégante et plus verbeuse, puis de passer à autre chose
    • Cette fonctionnalité me semble prématurée. Idéalement, elle devrait arriver après l’introduction des lambdas et des packs de paramètres génériques
      La prise en charge actuelle des génériques dans Go n’est pas assez sophistiquée pour la soutenir, ce qui impose des limitations étrangement artificielles
    • Je suis également partagé. Cela ne réduit même pas forcément le nombre de lignes de code par rapport au passage d’une fonction inline
  • L’ajout de sql.Null[T] est une bonne chose. Je l’utiliserai probablement dans les nouveaux projets
    Dans mon travail actuel, je dépends de null de sqlboiler [0], dont l’API est très similaire. Il se comporte comme sql.Null, mais ajoute une méthode IsSet() bool qui indique si la valeur a déjà été explicitement définie, ce qui aide à distinguer un « null intentionnel » d’un « null parce que non initialisé ». Ça a l’air bien, mais en pratique je ne l’ai jamais utilisé

    • Je me demande dans quels cas on a besoin de savoir si un null est intentionnel. La base de données n’a pas de type correspondant, non ?
  • J’ai hâte de réexaminer le mux de la bibliothèque standard pour voir s’il serait aussi possible de supprimer chi. C’est vraiment appréciable de voir ce genre de fonctionnalité arriver dans la bibliothèque standard

    • J’étais venu dire exactement la même chose. chi a été vraiment excellent dans plusieurs projets, au point que j’en oubliais presque son existence tant il semblait naturel
      Quand ce genre de fonctionnalité entre dans la bibliothèque standard, on a la garantie qu’elle sera toujours maintenue et qu’une même approche sera utilisée dans beaucoup de programmes Go
  • Je me demande, quand on utilise Go en production, si l’on peut passer rapidement aux dernières versions ou si l’on reste coincé sur d’anciennes releases
    En regardant des projets publics, j’ai l’impression qu’ils évitent souvent les fonctionnalités relativement récentes. Même sans utiliser les génériques, j’aimerais utiliser any, apparu avec Go 1.18, plutôt que l’ancien interface{}, et j’ai aussi entendu dire que le premier était plus « idiomatique »

    • Chez Square, nous attendons généralement la première release corrective avant de mettre à jour la version par défaut du monorepo Go. Les propriétaires d’applications individuelles peuvent mettre à jour plus tôt
      Cette fois, les choses se sont un peu compliquées : l’équipe Go a sorti la 1.22 avant que nous ayons passé la version par défaut à la 1.21. À ce stade, attendre relève presque de la convention, et il est rare qu’une release de Go casse quelque chose
    • Nous avons tendance à attendre d’avoir un problème adapté avant d’expérimenter les nouvelles fonctionnalités, mais nous mettons à jour la version du compilateur presque immédiatement après une release
      Par exemple, pour les génériques, nous avons attendu environ six mois avant de les utiliser réellement, afin de vérifier leur utilité, l’expérience développeur et l’absence d’impact important sur les temps de build et de test
    • Dans mon entreprise, les conteneurs en production, c’est-à-dire les services SaaS/API, peuvent être librement mis à jour par les développeurs vers les builds les plus récents
      Mais nous fournissons aussi des applications utilisateur final en Go, et celles-ci doivent rester en 1.20 pour des raisons de compatibilité avec d’anciens OS clients. Comme il y a eu une coupure assez importante en 1.21, il est probable que nous restions en 1.20 pendant quelques années
    • Notre CI compile et teste avec le conteneur latest de Go, et comme la suite de tests est assez solide, les mises à jour se font en pratique automatiquement. Si quelque chose ne va pas, le pipeline casse, et nous décidons alors soit d’épingler la dernière version fonctionnelle, soit de retarder la mise à jour jusqu’à corriger le changement cassant
      Tous les quelques années, une nouvelle version révèle parfois un bug subtil de cas limite entre Go et un package tiers, mais par définition ce type de bug a échappé à la suite de tests ; il est donc probable qu’une mise à niveau manuelle ne l’aurait pas détecté non plus
    • On migre tout de suite
      Si la prise en charge d’anciens compilateurs Go vous inquiète, vous pouvez ajouter des conditions de build pour les anciennes versions et définir ce qui manque. Par exemple, sous // +build !go1.7, on peut mettre type any = interface{}