1 Punkte von GN⁺ 2024-02-08 | 1 Kommentare | Auf WhatsApp teilen
  • for-Schleifenvariablen werden nun bei jeder Iteration neu erzeugt, um unbeabsichtigte Sharing-Bugs zu vermeiden; außerdem ist range über Ganzzahlen möglich
  • Die Kompatibilität hält das Go-1-Kompatibilitätsversprechen ein; es wird erwartet, dass nahezu alle Go-Programme wie bisher kompilieren und laufen
  • In Workspaces kann ein mit go work vendor erzeugtes vendor-Verzeichnis verwendet werden; wenn ein Workspace-vendor vorhanden ist, wird -mod=vendor zum Standardwert
  • Unterstützung für go get außerhalb von Modulen im Legacy-GOPATH-Modus mit GO111MODULE=off eingestellt; andere Build-Befehle wie go build und go test funktionieren weiterhin für Legacy-GOPATH-Programme
  • Die Runtime legt typbasierte GC-Metadaten nun näher an Heap-Objekten ab, wodurch sich die CPU-Performance von Go-Programmen um 1–3 % verbessert und der Speicher-Overhead vieler Programme um etwa 1 % sinkt
  • Die Adressausrichtung einiger Objekte kann sich von bisher 16 Byte oder mehr auf 8 Byte ändern; Assembly-Code, der eine höhere Ausrichtung voraussetzt, muss angepasst werden; als temporärer Workaround kann GOEXPERIMENT=noallocheaders verwendet werden
  • PGO-Builds können mehr Aufrufe devirtualisieren; bei einer repräsentativen Sammlung von Go-Programmen verbessert sich die Runtime-Performance mit aktiviertem PGO um 2–14 %
  • Mit math/rand/v2 wurde das erste v2-Paket in die Standardbibliothek aufgenommen; es übernimmt Read nicht, bietet einen globalen Generator mit immer zufälligem Seed, ChaCha8- und PCG-Quellen sowie die generische Funktion N
  • Routing-Patterns von net/http.ServeMux unterstützen Methoden und Wildcards; bei Patterns mit {} und der Verarbeitung escapeter Pfade gibt es kleine rückwärtsinkompatible Änderungen, das frühere Verhalten kann mit GODEBUG=httpmuxgo121=1 wiederhergestellt werden
  • Die standardmäßige Mindestversion für crypto/tls-Server wird, sofern nicht angegeben, auf TLS 1.2 geändert; bei Pre-TLS-1.3-Handshakes werden Cipher Suites ohne ECDHE standardmäßig nicht mehr angeboten
  • Der Execution Tracer wurde grundlegend überarbeitet: Auf den meisten Plattformen nutzt er die OS-Uhr und bietet in Segmente aufgeteilte, streambare Traces, die vollständige Dauer von Syscalls sowie Informationen zum OS-Thread, auf dem eine Goroutine ausgeführt wurde
  • Die Web-UI des trace-Tools wurde im Rahmen der Unterstützung für den neuen Tracer aktualisiert und unterstützt bei Traces von Programmen, die mit Go 1.22 oder neuer gebaut wurden, eine threadorientierte Ansicht sowie die Anzeige der vollständigen Syscall-Dauer
  • vet passt die Loop-Closure-Warnungen an die neue Bedeutung von Schleifenvariablen in Go 1.22 an und ergänzt Warnungen für append ohne genutzten Wert, nicht verzögertes time.Since in defer sowie nicht übereinstimmende Key/Value-Paare in log/slog
  • Für das Bootstrap ist mindestens das letzte Point Release von Go 1.20 erforderlich; Go 1.24 wird voraussichtlich mindestens das letzte Point Release von Go 1.22 verlangen
  • Unter darwin/amd64 erzeugt die Go-Toolchain standardmäßig PIE; Go 1.22 ist das letzte Release, das unter macOS 10.15 Catalina läuft, und Go 1.23 wird macOS 11 Big Sur oder neuer erfordern
  • In go/types wurden der Typ Alias und die Funktion Unalias hinzugefügt; bei gotypesalias=1 müssen Clients den Typ Alias verarbeiten
  • slices.Delete, DeleteFunc, Compact, CompactFunc und Replace setzen Elemente im verkleinerten Bereich auf zero; Insert löst immer eine Panic aus, wenn i außerhalb des gültigen Bereichs liegt
  • Windows-Funktionen zum Lesen von Verzeichnissen lesen Einträge stapelweise, um die Zahl der Systemaufrufe zu reduzieren; die Performance verbessert sich um bis zu 30 %

1 Kommentare

 
GN⁺ 2024-02-08
Hacker-News-Meinungen
  • In den letzten drei Jahren habe ich hauptsächlich TypeScript verwendet und erst vor Kurzem mit Go angefangen. Anfangs war ich wegen fehlender Array-Funktionen und des weniger flexiblen Typsystems etwas besorgt.
    Als ich dann aber wieder ein TypeScript-Projekt für ein Team von 4–5 Personen aufgesetzt habe, ging extrem viel Zeit für Linting, die Wahl einer Server-Routing-Library, die Server-Auswahl, Coding Standards, grundlegendes Error Handling, eigene Fehler oder erzwungene Result-Typen, um die Hölle verschachtelter try/catch-Blöcke zu vermeiden, Tests und Mocking, Prisma-Konfiguration usw. drauf.
    Ich mag TypeScript zwar, aber es ist wirklich angenehm, eine Sprache mit hervorragender Standardbibliothek, echter Typsicherheit und eingebauten Coding Standards zu nutzen. Go hat auch seine Eigenheiten, ist aber ziemlich gut, und dass jetzt sogar Routing in die Standardbibliothek aufgenommen wurde, nimmt einem eine weitere Sache ab, um die man sich kümmern muss. Jetzt warte ich nur noch auf map/filter/find-Funktionen für Slices.

    • In einem CTO-Newsletter wurden kürzlich TypeScript- und Go-Releases verglichen, mit Go 1.22 als Beispiel.
      TypeScript wird mit jedem Release für Power User immer komplexer, während Go fehlende Dinge wie range über Integer ergänzt und dadurch die Nutzung einfacher macht.
      Es ist ein bisschen wie bei Spiel-Fortsetzungen, die immer mehr Settings und Mechaniken anhäufen, bis für neue Nutzer ein Reset nötig wird. Bei Programmiersprachen ist das schwer, daher ist es gut, dass Go sich dessen bewusst ist.
    • Empfinde ich ähnlich. Was mich bei Go gehalten hat, war die erstaunlich einheitliche Build-Toolchain, und was man mit go:embed und go:generate machen kann, beeindruckt mich jedes Mal, wenn ich es in anderen Projekten sehe.
      Auch die golang.org/x-Pakete sind großartig, weil Implementierungen zu praktisch allen internetbezogenen RFCs direkt einsatzbereit sind.
    • Bis map/filter/find für Slices kommen, kann man die von lodash inspirierte lo-Library verwenden.
      In Go-Projekten ist sie mein gern genutztes Schweizer Taschenmesser: https://github.com/samber/lo
    • Gerade weil es nicht extrem viele Features gibt, fallen kleine Entscheidungen weg, und entgegen der Intuition ist man damit ziemlich produktiv.
    • Ich habe eine Zeit lang bei einem Kunden gearbeitet, der TypeScript nutzte, und irgendwann nannte ich es „Tricks Driven Development“.
      Immer wenn man etwas tun wollte und die Dokumentation las, kam kurz darauf jemand und zeigte einem einen brauchbaren Trick, der nicht in der Doku stand.
  • Wenn die offiziellen Release Notes etwas trocken wirken: Ich habe eine interaktive Version erstellt: https://antonz.org/go-1-22

    • Das Problem mit gemeinsam genutzten Variablen in for-Schleifen habe ich anfangs nicht sofort verstanden; es hat geholfen, es selbst auszuführen und das unerwartete Ergebnis zu sehen.
    • Die Beispiele zu Compact und Replace sind verwirrend; ich bin mir nicht sicher, ob das so stimmt.
  • Ich nutze Go seit über neun Jahren und habe in den letzten vier Jahren wegen Flutter viel Dart verwendet. Meiner Ansicht nach haben die beiden Sprachen eine genau gegensätzliche Haltung zu Komplexität. Dart versucht, jedes mögliche Feature hinzuzufügen, Go tut das Gegenteil.
    Erstens gibt es in Dart viel zu viele Möglichkeiten, Werte innerhalb einer Klasse zu initialisieren, was viel Zeit kostet. Je nach Präfix final/const/static/late unterscheiden sich Konstruktoren, Factories, initState() von Flutter-StatefulWidget und andere Initialisierungsorte; beim Refactoring muss man die Initialisierung oft stark überarbeiten. Getter, bei denen Funktionen wie Variablen aussehen, sorgen in neuen Codebases ebenfalls für zusätzliche Verwirrung.
    Zweitens hat Dart viele Features, mit denen sich Maps, Streams usw. gut in eine einzige Zeile quetschen lassen, wodurch man schnell versucht ist, schwer verständlichen Einzeiler-Code zu schreiben. Beim Debuggen oder wenn Junior-Entwickler auf Hacks wie firstWhereOrNull und optionale Parameter wie orElse: () => null stoßen, sind sie jedoch sehr verwirrt. Auch undurchsichtige Null-Safety- oder Typfehler halten einen oft auf, und es ist eine echte Erleichterung, das in eine einfache Go-artige for-Schleife umzuschreiben.

    • Als Dart-Tipp: Für Dinge, die nicht direkt mit der UI zu tun haben, sollte man initState besser nicht verwenden. Ich vermeide es, außer es geht beispielsweise nur darum, einen Hint für ein Textfeld zu setzen.
      Die meisten Flutter-Seiten haben entweder sehr wenig zu tun, oder der Parent erstellt ein MyPageController-Objekt, initialisiert es wie gewünscht und lässt die Child-Seite anhand dieses Controllers arbeiten. Wenn ein Nutzer etwa auf einer Listenseite ein Element bearbeiten will, erstellt man einen Controller, legt das Element hinein und übergibt ihn an die Child-Seite; nach der Rückkehr kann der Parent anhand des Elements im Controller oder anhand von Variablen und Callbacks entscheiden, wie sich die UI ändern soll.
      So lässt sich die Interaktion zwischen Widgets fein steuern, ohne Dinge wie InheritedWidget anzufassen. Natürlich ist es gut, das zusammen mit einer State-Management-Library zu nutzen, aber in der Praxis tut man das oft auch nicht.
    • Ich stimme zu, dass die Einfachheit von Go sein größter Vorteil ist. Bei range ist es ähnlich: Am Ende tippt man ungefähr so viele Zeichen wie bei einer Comprehension à la forEach(f), nur wirkt es länger, weil es auf drei Zeilen verteilt ist; wenn man es ausschreibt, ist es keine große Sache.
      Was Initialisierung angeht, würde ich mir allerdings eine idiomatische Möglichkeit wünschen, Struct-Felder in Go auf bestimmte Werte zu initialisieren. Dass es keine Konstruktoren gibt, ist in Ordnung, aber manchmal möchte man, dass ein bool standardmäßig auf true initialisiert wird.
    • initState ist ein Override von StatefulWidget und unterscheidet sich nicht von Lifecycle-Events in anderen Frameworks; es ist ein Flutter-Konzept und sollte nicht mit Konstruktoren verwechselt werden.
      Man kann sich zwar fragen, ob man einen normalen Konstruktor oder eine Factory verwenden sollte, aber wegen initState lässt sich kaum behaupten, Go sei überlegen.
    • Man sollte darauf optimieren, dass teure Fachleute prägnant miteinander kommunizieren können. Mächtige Syntax-Werkzeuge Anfängern wegzunehmen, verhindert nur, dass sie Expertise aufbauen.
  • Wenn io.Copy von TCPConn nach UnixConn kopiert, verwendet es, wenn möglich, den Linux-Systemaufruf splice(2) und nutzt die neue Methode TCPConn.WriteTo.
    Durch ein Interface-Upgrade wurde also erneut transparent mehr Zero-Copy-I/O ermöglicht. Der Effekt, den das io-Paket mit diesem Muster erzielt, ist wirklich groß.

    • In der Go-Standardbibliothek gibt es sehr viele solcher Abstimmungen. Das ist nicht unbedingt schlecht, aber solche Sonderbehandlungen sind nicht skalierbar, sodass die Integration mit Nicht-Standardbibliotheken weniger zufriedenstellend wird.
      Trotzdem liegt es im erwartbaren Rahmen.
  • Verbesserte Routing-Patterns sind eine gute Verbesserung, gerade jetzt, wo die Zukunft von Gorilla Mux unsicher ist.
    Allerdings bricht diese Änderung in kleinem Umfang die Abwärtskompatibilität: Es gibt offensichtliche Stellen, etwa dass sich das Verhalten von Patterns mit { und } ändert, und weniger offensichtliche, etwa die Behandlung escapeter Pfade. Gesteuert wird das über das Feld httpmuxgo121 in GODEBUG; mit httpmuxgo121=1 lässt sich das frühere Verhalten wiederherstellen.
    Ich frage mich aber, warum das nicht mit dem Go-1-Kompatibilitätsversprechen kollidiert: „Programme, die nach der Go-1-Spezifikation geschrieben wurden, werden während der Lebensdauer dieser Spezifikation weiterhin unverändert kompilieren und korrekt laufen.“

    • Die Art, das Go-1-Kompatibilitätsversprechen zu umgehen, besteht darin, solche abwärtsinkompatiblen Änderungen an den Wert der go-Direktive in go.mod zu koppeln.
      Ab 1.22 bekommt man das neue Library-Verhalten, andernfalls das alte. Wie gut das in der Praxis funktioniert, wird sich zeigen.
    • Normalerweise durchsucht das Go-Team GitHub und Open-Source-Programme, bevor es etwas kaputt macht, um zu sehen, wie Leute es verwenden. Vermutlich haben sie festgestellt, dass { und } in HTTP-Handler-Pfaden kaum genutzt werden.
      Außerdem gibt es eine Möglichkeit, das neue Verhalten abzuschalten, sodass keine Codeänderung erzwungen wird; man muss allerdings eine neue Umgebungsvariable setzen.
      Die Änderung der Semantik von for-Schleifen in diesem Release ist ein ähnliches Beispiel, im Grunde ebenfalls eine Breaking Change. Trotzdem kompilieren und laufen alle Go-Programme weiter, nur manches Verhalten ändert sich leicht. Go hat einen pragmatischen Ansatz gewählt, und ich denke, das war einer der Gründe für seinen Erfolg.
    • Das halte ich für einen seltenen Fehler in Go. Wenn man schon go.mod nutzt, um versionsabhängig etwas zu brechen, sollte man es wenigstens auf bessere Weise brechen.
      Wenn man go.mod auf 1.22 ändert, wäre es viel besser, der Code würde nicht kompilieren, statt subtile Laufzeitprobleme aufspüren zu müssen.
    • Sonst verliert man den kurzen, starken Werbespruch. Klein oder nicht: Es ist eine inkompatible Änderung.
  • In Go 1.22 erzeugt jede Iteration einer Schleife eine neue Variable.
    Das wurde hier schon früher diskutiert: https://news.ycombinator.com/item?id=33160236 – Go: Redefining For Loop Variable Semantics (2022)

  • Vielleicht bin ich altmodisch, aber die Ergänzung von range über Funktionen gefällt mir nicht. Der Komfort ist nicht groß genug, um die zusätzliche Komplexität in der Sprache zu rechtfertigen, und der funktionale Stil scheint nicht zu Gos explizitem, imperativem und wortreichem, aber funktionsarmem Stil zu passen. Das war meiner Meinung nach eine der großen Stärken von Go.
    Aus demselben Grund halte ich auch range über Integers für die falsche Richtung. Gos schlanker Funktionsumfang und die geringe kognitive Last waren immer eine zentrale Stärke und ein Unterscheidungsmerkmal.

    • Derzeit ist dieses Feature optional und experimentell und muss explizit aktiviert werden. Falls es standardmäßig aktiviert wird, fände ich einen Abschaltmechanismus gut, der Entwickler davon abhält, es zu verwenden, wenn es nicht allgemein üblich ist.
      Eines der großen Probleme in der Softwareentwicklung ist, Entwickler davon abzuhalten, Komplexität hinzuzufügen oder ihr Können demonstrieren zu wollen. Besonders Entwickler am Anfang ihrer Laufbahn neigen dazu, Lösungen übermäßig zu verkomplizieren, statt ein Problem geradlinig, wenn auch weniger elegant und ausführlicher, zu lösen und weiterzumachen.
    • Dieses Feature fühlt sich noch verfrüht an. Idealerweise sollte es erst nach Lambdas und generischen Parameter-Packs kommen.
      Gos aktuelle Generics-Unterstützung ist nicht ausgefeilt genug, um das zu tragen, wodurch seltsam erzwungene Einschränkungen entstehen.
    • Ich sehe das ebenfalls zwiespältig. Im Vergleich zum Übergeben einer Inline-Funktion spart es möglicherweise nicht einmal Codezeilen.
  • Die Ergänzung von sql.Null[T] ist gut. In neuen Projekten werde ich das vermutlich verwenden.
    In meiner aktuellen Arbeit hängen wir von null aus sqlboiler [0] ab, dessen API sehr ähnlich ist. Es verhält sich wie sql.Null, hat aber zusätzlich eine Methode IsSet() bool, die angibt, ob ein Wert explizit gesetzt wurde, und dabei hilft, „absichtliches null“ von „null, weil nicht initialisiert“ zu unterscheiden. Klingt gut, aber in der Praxis habe ich es noch nie benutzt.

    • Ich frage mich, wann man wissen muss, ob null absichtlich ist. In der Datenbank gibt es dafür doch keinen entsprechenden Typ, oder?
  • Ich freue mich darauf, mir den Mux der Standardbibliothek wieder anzusehen und vielleicht sogar chi zu entfernen. Es ist wirklich schön, wenn solche Funktionen in die Standardbibliothek kommen.

    • Genau das wollte ich auch sagen. chi war in einigen Projekten wirklich großartig und fühlte sich so natürlich an, dass man seine Existenz fast vergessen hat.
      Wenn solche Funktionen in die Standardbibliothek kommen, ist gewährleistet, dass sie immer gepflegt werden und viele Go-Programme denselben Ansatz verwenden.
  • Wenn man Go in Produktion einsetzt, frage ich mich, ob man schnell auf die neueste Version wechseln kann oder ob man an ältere Releases gebunden bleibt.
    Bei öffentlichen Projekten sieht es oft so aus, als würden relativ neue Features vermieden. Auch wenn man keine Generics verwendet, würde ich statt des alten interface{} gern das mit Go 1.18 eingeführte any nutzen; ich habe auch gehört, Letzteres sei „idiomatischer“.

    • Bei Square warten wir normalerweise auf das erste Point Release, bevor wir die Standardversion im Go-Monorepo anheben. Einzelne Application Owner dürfen früher aktualisieren.
      Diesmal war es etwas verzwickt: Das Go-Team hat 1.22 veröffentlicht, bevor wir den Standard auf 1.21 angehoben hatten. Inzwischen ist das Warten fast nur noch Gewohnheit; es ist selten, dass ein Go-Release etwas kaputt macht.
    • Bei neuen Features warte ich mit Experimenten eher, bis ich ein passendes Problem sehe, aber die Compiler-Version aktualisiere ich fast unmittelbar nach dem Release.
      Bei Generics haben wir zum Beispiel etwa sechs Monate gewartet, bevor wir sie tatsächlich genutzt haben, um sicherzustellen, dass Nutzen, Developer Experience sowie Auswirkungen auf Build- und Testgeschwindigkeit nicht gravierend sind.
    • Bei der Arbeit können Entwickler die laufenden Container, also SaaS/API-Services, frei auf aktuelle Builds heben.
      Wir liefern aber auch Endnutzer-Apps in Go aus, und dort müssen wir wegen der Kompatibilität mit älteren Client-Betriebssystemen bei 1.20 bleiben. Da es in 1.21 einen erheblichen Schnitt gab, bleiben wir in diesem Bereich wahrscheinlich noch einige Jahre auf 1.20.
    • Unsere CI baut und testet mit dem latest-Container von Go, und die Testsuite ist ziemlich solide, sodass Updates praktisch automatisch erfolgen. Wenn etwas nicht passt, bricht die Pipeline, und dann entscheiden wir, ob wir auf die letzte funktionierende Version pinnen oder das Update aufschieben, bis die Breaking Change behoben ist.
      Etwa alle paar Jahre zeigt sich zwischen einer neuen Version und einem Third-Party-Paket ein subtiler Edge-Case-Bug; solche Bugs haben per Definition aber die Testsuite umgangen, wären also wahrscheinlich auch bei einem manuellen Upgrade nicht aufgefallen.
    • Wir wechseln sofort.
      Wenn dir die Unterstützung alter Go-Compiler Sorgen macht, kannst du Build Conditions für alte Versionen setzen und Fehlendes definieren. Zum Beispiel kannst du unter // +build !go1.7 ein type any = interface{} platzieren.