Go lernen an ein paar verregneten Tagen

Besonderheiten und Schwierigkeiten

Die Programmiersprache Go ist dafür bekannt, dass sie leicht zu lernen ist. Diese Erfahrung habe ich auch gemacht. Allerdings gibt es ein paar Eigenheiten, die sich für Programmierer*innen mit Erfahrungen in anderen Sprachen erst einmal ungewohnt anfühlen. Auf einige dieser Besonderheiten möchte ich in diesem Blogpost eingehen.

Ein kleiner Disclaimer vorweg: Ich behandele recht einfache Sprachfeatures. Für einen tiefergehenden Einstieg in die Sprache kann ich den Artikel meiner INNOQ-Kollegen auf Heise.de empfehlen.

Schleifen

In der Sprache Go gibt es, anders als in anderen Sprachen, nur die for Schleife. Der Grund dafür ist, dass die Sprache möglichst einfach und übersichtlich gehalten werden soll. Es gibt also beispielsweise kein .each wie in Ruby, und keine do { … } while-Schleife wie in Java. Das mag sich ungewohnt anfühlen. Mit ein bisschen Übung ist es aber kein Problem, sich auf diese Beschränkung einzulassen. Die Ausgabe einer Anzahl von Integers sieht beispielsweise folgendermaßen aus:

limit := 10
for i := 0; i <= limit; i++ {
    fmt.Println(i)
}

Die for-Schleife verhält sich so wie in anderen Sprachen. Anders als in Java schreibt man in Go keine Klammern um die drei Elemente, die die Bedingungen für die for-Schleife festlegen. Eine andere Möglichkeit, dieselbe Funktionalität in einer for-Schleife zu schreiben, wäre diese hier:

limit := 10
i := 0
for i <= limit {
	fmt.Println(i)
	i++
}

Bei dieser Form der Schleife wird nur der Endwert angegeben. Diese for-Schleife verhält sich also gewissermaßen wie eine while-Schleife. Lässt man den Endwert ganz weg, erhält man eine endlose Schleife, aus der man mit einem break oder return ausbrechen kann. Soweit so vertraut.

Strings und Runes

Bei dem Iterieren über Strings bin ich jedoch über ein paar Besonderheiten gestolpert. Wenn ich mir zum Beispiel die einzelnen Chars eines Strings s ausgeben lassen will, habe ich mit diesem Codeschnipsel hier

s := "rain ☂"
for i := 0; i < len(s); i++ {
	fmt.Println(s[i])
}

die folgenden zwei Probleme:

  1. len(s) bezieht sich nicht auf die Anzahl an Characters, sondern die Anzahl an Bytes. Möchte ich die Anzahl von Characters in dem String erfahren, müsste ich utf8.RuneCountInString(s) verwenden.

  2. Ich bekomme keine Buchstaben ausgeprintet, sondern für den String "rain ☂" folgenden Output:

114
97
105
110
32
226
152
130

Das erste Problem lässt sich leicht umgehen, wenn man einmal darüber Bescheid weiß, dass len(s) die Anzahl an Bytes zählt. Die zweite Eigenheit ist bei genauerer Betrachtung auch kein Problem, sondern gewissermaßen ein Feature: Die ausgegebenen Werte, die ich bekomme, stehen für die einzelnen Bytes des Strings, und sie repräsentieren hier Unicode code points. Das gilt zumindest für die ersten fünf Zahlen im Output. Diese ersten fünf Zahlen kodieren jeweils einen Buchstaben innerhalb eines Bytes. Das ist ASCII, oder besser gesagt UTF-8. Was ist aber mit den letzten drei Bytes? Da stimmt die Kodierung offensichtlich nicht?

Das liegt daran, dass ich mir den String byteweise habe ausgeben lassen, manche UTF-8 kodierten Zeichen aber mit mehr als einem Byte repräsentiert werden. Besser ist es, einen String rune für rune auszugeben. Was sind Runes? Runes repräsentieren in Go eben diese Unicode Code Points. Genauer genommen sind sie ein Typ-Alias für den Datentypen int32, da es möglich ist, in dem Datentypen int32 alle Unicode-Zeichen unterzubringen. Zum Verständnis von strings und runes in Go im Detail empfehle ich, diesen Artikel von Rob Pike zu lesen. Um mir den String nun in Runes ausgeben zu lassen, verwende ich die folgende Schleife:

s := "rain ☂"
for _, r := range s {
 	fmt.Println(r, string(r))
}

Dabei benutze ich ein range um über den String zu iterieren. Anstelle des Indexes steht der Platzhalter _, da ich den Index selbst nicht benötige. Der Übersicht halber lasse ich mir mit string(r) auch die Buchstaben selbst ausgeben. Als Ausgabe bekomme ich:

114 r
97 a
105 i
110 n
32
9730 ☂

Der letzte Wert ist nun 9730 . Das ist – tada – der Unicode Code Point für ☂ (dieser Blogpost lässt unschwer erkennen, dass er an Regentagen geschrieben wurde). Ich bekomme also nun für jeden Buchstaben bzw. jedes Symbol die einzelnen Unicode Code Points, und kann diese weiter verarbeiten. Die enge Verzahnung von Go und UTF-8 macht es außerdem möglich, Unicode im Source Code selbst zu verwenden. Das gilt jedoch leider nicht für alle Unicode-Zeichen: Kombinierte Zeichen, wie sie in vielen Sprachen vorkommen, können leider nicht als Identifier verwendet werden.

Wo finde ich Funktion xy?

Noch eine kurzer Hinweis zum Schluss: Ungewohnt ist bei Go sicherlich auch, dass in der Standard-Library einige grundlegende Funktionen fehlen. Ein Beispiel ist die Funktion zum Auffinden eines Wertes in einem slice, also einem dynamisch erweiterbaren Array. In anderen Sprachen heißt diese Funktion zum Beispiel include? oder contains(). In Go gibt es sie erstmal nicht: Sie muss selbst implementiert werden. Möglicherweise liegt das daran, dass eine solche Funktion die lineare Laufzeit O(n) hätte, und bei sehr großen Slices entsprechend nicht performant wäre. Für große Datenmengen, in denen etwas gesucht und gefunden werden soll, ist eine map im Zweifel die bessere Wahl.

Einige andere Funktionalitäten sind standardmäßig in Go schon enthalten. Die Standard-Library enthält z.B. mit dem Package net/http einen HTTP-Client und -Server, der sehr gut benutzbar ist. Daran sieht man, dass Go ursprünglich entwickelt wurde, um die Umsetzung verteilter Systeme und großer Infrastrukturprojekte zu vereinfachen.

Linkliste Go-Lernmaterialien

Zu guter Letzt möchte ich hier noch eine Liste an Lernmaterialien für Go zur Verfügung stellen. Darunter finden sich unterschiedliche Formate - von Online-Lernplattformen über Webseiten mit Code Snippets bis hin zu einem Online-Buch.

TAGS

Kommentare

Um die Kommentare zu sehen, bitte unserer Cookie Vereinbarung zustimmen. Mehr lesen