Uzyskaj dostęp do tej i ponad 180000 książek od 9,99 zł miesięcznie
Sie haben schon Erfahrung mit objektorientierten Programmiersprachen und wollen sich jetzt Googles Programmiersprache Go genauer ansehen? Dann ist dieses Buch genau das Richtige für Sie! Denn Sie steigen direkt in die Besonderheiten von Go ein und lernen das Ökosystem rund um Tools und Testing kennen. Dabei liegt stets ein Fokus auf der Codequalität, damit Ihr Code von Anfang an den gängigen Code-Konventionen der Go-Community entspricht. Das alles lernen sie nicht nur mit grauer Theorie, sondern direkt an der Tatstatur mit Übungsaufgaben und Beispielprojekten.
Ebooka przeczytasz w aplikacjach Legimi na:
Liczba stron: 360
Odsłuch ebooka (TTS) dostepny w abonamencie „ebooki+audiobooki bez limitu” w aplikacjach Legimi na:
Andreas Schröpfer ist seit über zehn Jahren in der IT-Beratung tätig und seit 2015 begeisterter Gopher. Er ist Contributor bei mehreren Open-Source-Projekten – darunter Go Buffalo. Er gibt Workshops zu Go, ist Mentor bei excercism.io und unterrichtet auch auf Udemy.
Zu diesem Buch – sowie zu vielen weiteren dpunkt.büchern – können Sie auch das entsprechende E-Book im PDF-Format herunterladen. Werden Sie dazu einfach Mitglied bei dpunkt.plus+:
www.dpunkt.plus
Andreas Schröpfer
Einstieg in Go und das Go-Ökosystem
Andreas Schröpfer
Lektorat: Melanie Feldmann
Copy-Editing: Alexander Reischert, www.aluan.de
Satz: Da-TeX Gerd Blumenstein, Leipzig, www.da-tex.de
Herstellung: Stefanie Weidner
Umschlaggestaltung: Helmut Kraus, www.exclam.de
Bibliografische Information der Deutschen Nationalbibliothek
Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.
ISBN:
Print 978-3-86490-713-5
PDF 978-3-96088-848-2
ePub 978-3-96088-849-9
mobi 978-3-96088-850-5
1. Auflage 2020
Copyright © 2020 dpunkt.verlag GmbH
Wieblinger Weg 17
69123 Heidelberg
Hinweis:
Dieses Buch wurde auf PEFC-zertifiziertem Papier aus nachhaltiger Waldwirtschaft gedruckt. Der Umwelt zuliebe verzichten wir zusätzlich auf die Einschweißfolie.
Schreiben Sie uns:
Falls Sie Anregungen, Wünsche und Kommentare haben, lassen Sie es uns wissen: [email protected]
Die vorliegende Publikation ist urheberrechtlich geschützt. Alle Rechte vorbehalten. Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder die Verwendung in elektronischen Systemen.
Es wird darauf hingewiesen, dass die im Buch verwendeten Soft- und Hardware-Bezeichnungen sowie Markennamen und Produktbezeichnungen der jeweiligen Firmen im Allgemeinen warenzeichen-, marken- oder patentrechtlichem Schutz unterliegen.
Alle Angaben und Programme in diesem Buch wurden mit größter Sorgfalt kontrolliert. Weder Autor noch Verlag können jedoch für Schäden haftbar gemacht werden, die in Zusammenhang mit der Verwendung dieses Buches stehen.
5 4 3 2 1 0
Als ich Ende 2012 das erste mal Go ausprobierte, war ich auf der Suche nach einer Alternative zu meinen Python-Skripten. Diese halfen mir bisher dabei, dass meine Programme auf unterschiedlichen Systemen liefen. Ich wollte aber nichts mehr installieren müssen. Für dieses Szenario war Go ideal und ich fing an, mich mehr und mehr mit Go zu beschäftigen. Besonders die einfach Syntax half mir dabei, schnell Fortschritte zu machen. Bei Fragen fand ich immer sehr schnell Hilfe in der Community. Vor allem der freundliche und geduldige Umgang miteinander haben mich dabei besonders beeindruckt. Das hat mich auch dazu ermutigt, anderen Menschen Go beizubringen. Neben kleinen Workshops und Vorträgen hatte ich mich auch entschlossen Udemy-Kurse zu erstellen, um meine Reichweite zu erhöhen. Als ich dann vom dpunkt.verlag gefragt wurde, ob ich über Go auch ein Buch schreiben würde, musste ich nicht lange zögern. Das Ergebnis siehst Du hier!
Dieses Buch ist eine Mischung aus Theorie und mehreren kleinen Projekten. Mit den Projekten möchte ich Dich an die Hand nehmen und Dir zeigen, wie Du verschiedene Aufgaben mit Go lösen kannst. Die Projekte sind so gewählt, dass Du sie schnell eigenständig nachprogrammiert kannst. Ich würde jedem empfehlen, die Beispiele einmal selbst abzutippen.
Alle Ungeduldigen können den Code von GitHub herunterladen. Das Repository hat folgende URL: https://github.com/gobuch/code
Das Maskottchen von Go ist der Gopher. Dieser taucht hin und wieder in den Kapiteln auf. Die Abbildungen wurden von mir nach einem Vorbild von Renee French erstellt.
Das Buch zu konzipieren und zu schreiben hat viel Zeit eingenommen, deshalb möchte im mich ganz besonders bei meiner Familie bedanken, die mir diese Zeit gegeben hat. Ein großes Dankeschön geht auch an Alex und Marcel für das technische Feedback. Ein ganz besonderer Dank geht an meine Lektorin Melanie Feldman, ohne die dieses Buch sicher gar nicht zustande gekommen wäre.
Troisdorf im März 2020
Andreas Schröpfer
Vorwort
1Einleitung
1.1Ziel dieses Buches
1.2Die Geschichte von Go
1.3Installation
1.4Sicherheit und Patches
1.5Editoren für den Go-Werkzeugkasten
1.6Der Spielplatz für Gopher
1.7Hello World
1.8Eine lesbare technische Spezifikation
1.9Ausgabe mit dem fmt-Paket
2Vorstellung der Syntax
2.1Wörter, Funktionen und Typen von Go
2.2Variablen
2.3Konstanten
2.4Pointer
2.5Eigene Typen
2.6Typumwandlung
2.7Zusammengesetzte Strukturen
2.8Funktionen
2.9Objektorientierung mit Methode
2.10Exportiert und nicht exportiert
2.11Arrays
2.12Slices
2.13Das Slice als Pointer
2.14Maps
2.15if
2.16switch
2.17for
2.18Labels und goto
2.19Blank Identifier
2.20UTF-8, strings und runes
3Projekt: Command Line Interface
3.1Einleitung
3.2gocat – File-Ausgabe
3.3Den md5-Hash erzeugen
3.4Dateien und HTTP-Server als Quellen für gomd5
4Go Tooling
4.1Schnelle Hilfe mit go help
4.2Kompilieren und Installieren
4.3Umgebungsvariablen mit go env
4.4Ein Programm für jede Gelegenheit – Build Tags
4.5Wie Code formatiert wird – gofmt
4.6Automatische Imports mit goimports
4.7Dokumentation immer dabei – godoc
5Projekt: Ein einfacher Webloader
5.1Einleitung
5.2CLI – unser Interface
5.3HTTP-Request erstellen
5.4Implementierung des File-Outputs
5.5Ausgabe des HTTP-Headers
5.6Gültigkeit der übergebenen URL
6Eigene Pakete und Module
6.1Go-Code lebt in Paketen
6.2Paketnamen
6.3Die init()-Funktion
6.4Semantic Versioning
6.5Pakete leben in Modulen
6.6Der Workflow, seit es Module gibt
6.7Neuer bedeutet nicht immer besser
6.8Update unserer Abhängigkeit
6.9Neue Major-Version mit Modulen
7Projekt: Code generieren
7.1Einleitung
7.2Ein Tool, um Code zu generieren
7.3Template erstellen
7.4Anwenden von go generate
8Concurrency-Grundlagen
8.1Concurrency mit Go
8.2Parallelität im echten Leben
8.3Goroutinen
8.4Channels
8.5Einen Channel schließen
8.6Select
8.7Race Conditions und Data Races
9Concurrency Patterns
9.1Checkliste zu Goroutinen
9.2Goroutinen melden, wenn sie fertig sind
9.3Beenden von Goroutinen
9.4Context
9.5Prüfung eines geschlossenen Channels
9.6Pipelines
9.7Generator
9.8Fan-In und Fan-Out
9.9Channel of Channels
9.10Worker Pool
9.11Semaphore mit einem Buffered Channel
9.12State Machine
10Projekt: Go Concurrency
10.1Einleitung
10.2Command Line Interface
10.3Argumente parsen
10.4Befehle ausführen
10.5Abbruch mit context
10.6Verbesserung des Tools
11Testen und Benchmarks
11.1Tests in Go
11.2Subtests
11.3Tabellarische Tests
11.4Eigenes Testpaket
11.5Testen mit Beispielen
11.6Ein ganzes Projekt testen
11.7Benchmarks
11.8Syntax der Benchmarks
11.9Subbenchmarks
12Projekt: Image Resizer
12.1Einleitung
12.2Command Line Interface – Erstellen der Flags
12.3Größe erzeugen
12.4Bild verkleinern
12.5Filename prüfen
12.6Funktionen zusammenführen
12.7Refactoring in eine zusätzliche Funktion
12.8Eigener Fehlertyp
12.9Von sequentieller Ausführung zu nebenläufiger Ausführung
13Interfaces
13.1Bessere Abstraktion mit Interfaces
13.2Die richtige Interface-Erstellung
13.3Interne Abbildung der Interface-Typen
13.4Leeres Interface
13.5Vom Interface zum konkreten Typ
13.6Interface in andere Interfaces einbinden
13.7Interfaces in Strukturen einbinden
13.8Mocking und Tests mit io.Reader und io.Writer
14Projekt: Kopieren mit Reflection
14.1Einleitung
14.2Reflection in Go
14.3Beschreibung des Pakets
14.4Testfälle für unser Paket
14.5Umsetzung
14.6Verwenden von Tags
15Fehlerbehandlung
15.1Grundlagen
15.2Variablen und Konstanten
15.3Eigene Fehlertypen
15.4Einem Fehler Kontext hinzufügen
15.5Keine Panik
16Projekt: Ein einfacher Webserver
16.1Einleitung
16.2Das Modell für unseren Blog
16.3Der Webserver und seine Handler
16.4Templates erstellen
16.5Kommentarfunktion
16.6Files ausliefern
16.7API bereitstellen
16.8Template nur einmal parsen
16.9Nebenläufiger Job für den Index
16.10Ein paar kleine Verbesserungen
Willkommen in diesem Buch über die Programmiersprache Go. Hier wirst Du mit praktischen Beispielen und Übungen lernen, wie Du Programme in dieser Sprache erstellt. Zu Beginn der theoretischen Kapitel stehen ein paar Fragen, die Dir bereits verraten, welche Inhalte wichtig sind. In diesem allerersten Kapitel werde ich folgende Fragen beantworten:
Worum geht es in diesem Buch?
Was sind die Ziele dieses Buches?
Warum habe ich dieses Buch geschrieben?
In den folgenden Kapiteln möchte ich Dir die Programmiersprache Go vorstellen. Wir beginnen mit der Syntax und ein wenig allgemeiner Theorie. Damit das alles nicht zu trocken wird, gibt es zwischen den theoretischen Kapiteln kleine Projekte. Am Ende des Buches weißt Du, wie Du skalierbaren und nebenläufigen Code mit Go schreibst, wie Du die Dokumentation nutzt und wie Du eigene Pakete und Tools erstellen kannst.
Umfangreiche Werkzeuge erleichtern die Arbeit mit Go.
Go ist eine sehr junge Programmiersprache, zumindest verglichen mit C, C++ oder Java. Die Syntax der Sprache ist überschaubar klein und sehr gut dokumentiert. Außerdem hat das Go-Team neben der Sprache auch gleich noch einen großen Werkzeugkasten angelegt, der wirklich alles beinhaltet, was für professionelles Coding notwendig ist: Dokumentation, Test-Framework, statische Codeanalyse, Race Detector und vieles mehr. Alles ist direkt bei der Go-Installation dabei und steht über eine Open-Source-Lizenz jedermann frei zur Verfügung.
Go macht Spaß.
Dieses Zusammenspiel aus großer Einfachheit und frei zugänglichen Tools hat mich von der Sprache vollends überzeugt. Es macht sehr viel Spaß, Programme in Go zu schreiben und diesen Spaß auch an andere weiterzugeben. Genau diesen Enthusiasmus möchte ich mit diesem Buch vermitteln.
Wo wurde Go entwickelt?
Wer sind die Macher von Go?
Was waren die Ziele bzw. welche Probleme löst Go?
2007 bei Google
Die Sprache Go wurde 2007 bei Google entworfen. Die Personen hinter der Sprache sind Robert Griesemer, Rob Pike und Ken Thompson. Angeblich soll Go während des Kompilierens von C++-Programmen entstanden sein. Ganz so stimmt das nicht, doch die langen Kompilierzeiten waren ein wichtiger Auslöser für das Go-Projekt. In einem Interview hat Rob Pike einmal geschildert, dass er an einem komplexen Stück Code arbeitete und nach jeder Änderung 45 Minuten warten musste, bis das dazugehörige Programm kompiliert war. Ein weiterer Auslöser für Go war die schlechte Unterstützung für Multi-Core-Prozessoren der anderen Programmiersprachen. Denn Rob Pike hatte durch andere Projekte, z. B. Newsqueak, auf dem Gebiet der nebenläufigen Programmierung bereits einige Erfahrung gesammelt.
Über die Macher
Rob Pike und Ken Thompson waren bereits vor Go wichtige Namen der IT-Geschichte. Rob Pike ist z. B. einer der Köpfe hinter UTF-8 und Ken Thompson hatte seinerzeit die Sprache B entwickelt, den Vorgänger von C. Der Dritte im Bunde, Robert Griesemer, ist auch kein unbeschriebenes Blatt. Er arbeitete damals z. B. an der Optimierung der JavaScript-Engine V8.
Nach der initialen Zündung für Go konnten die drei Google davon überzeugen, dass es sinnvoll ist, Go als Sprache für Cloud-Server zu entwickeln. Wenn wir uns in späteren Kapiteln an manchen Stellen fragen, warum gewisse Features so in der Standardbibliothek implementiert sind und nicht anders, dann liegt das am ursprünglichen Verwendungszweck als Serversprache für Google.
Google hatte 2018 knapp 100.000 Mitarbeiter, wovon geschätzt ca. 60 % Entwickler waren. Bei einer so großen Anzahl von Entwicklern ergeben sich mehrere Probleme. Code wird in großen Organisationen nämlich mehr gelesen als geschrieben. Außerdem kommen bei Google auch laufend viele neue Entwickler ohne große Erfahrung hinzu. Deshalb sollte die neue Programmiersprache Go einfach zu erlernen und einfach zu lesen sein.
Go soll schnell zu lernen sein.
Die Syntax von Go ist deswegen an C und Java angelehnt. Dadurch können Entwickler mit Erfahrung in diesen Sprachen Go einfacher lernen. Um die Sprache so einfach wie möglich zu gestalten, wurde nur das Notwendigste in die Sprache aufgenommen. Go erfindet das Rad nicht neu, sondern ist wie C oder Java, aber eben auf das Notwendigste reduziert.
Seit 2009 ist Go Open Source.
Seit 2009 sind die Sprache und die dazugehörigen Werkzeuge Open Source und können somit kostenlos sowohl privat als auch professionell genutzt werden. Seit diesem Zeitpunkt gibt es eine aktive weltweite Community, die gemeinsam Go weiterentwickelt.
2012 erfolgte die Veröffentlichung der Version 1. Damit einher ging auch das Versprechen, dass es keine Breaking Changes mit einem neuen Release gibt. Wenn der Code unter 1.10 läuft, dann wird der auch unter 1.15 laufen. Dieses Versprechen gilt sowohl für die Syntax als auch für die Standardbibliothek. Es wird sehr ernst genommen und ist damit auch ein Garant für die Stabilität der Sprache.
Wo finde ich eine Installationsanleitung für Go?
Die Installation der Go-Umgebung ist abhängig vom jeweiligen System. Unter https://golang.org/doc/install sind die Installationsanleitungen und die notwendigen Vorraussetzungen für alle System aufgeführt. Eine Go-Installation beinhaltet den Compiler, das Go Tooling (siehe Kapitel 4) und die Standardbibliothek. Für macOS und Windows gibt es bereits vorgefertigte Installer, die bei der Installation alle notwendigen Umgebungsvariablen setzen. Für Linux müssen diese selbst gesetzt werden. Die dafür notwendigen Befehle befinden sich auch auf der Seite.
In diesem Buch sind die Beispiele und Projekte so aufgebaut, dass sie als Modul initialisiert werden. Go unterstützt dies jedoch erst seit der Version 1.11. Wir müssen also sicherstellen, dass auf Deinem System auch eine Version höher 1.11 installiert ist. Mit dem Befehl go version kannst Du die installierte Go-Version auf Deinem System prüfen.
Sollte auf Deinem System 1.10 oder niedriger vorhanden sein, empfehle ich, eine neuere Version zu installieren. Insbesondere bei manchen konservativen Linux-Distributionen kann es sein, dass über die Paketverwaltung im Standard nur ältere Versionen von Go vorhanden sind. Lokal zum Ausprobieren ist eine veraltete Version in Ordnung. Doch spätestens, wenn wir Programme für einen produktiven Betrieb kompilieren, ist es wichtig, immer die aktuellste Version zu besitzen.
Was müssen wir tun, wenn wir eine Sicherheitslücke entdecken?
Wie lange wird eine Go-Version mit Updates versorgt?
Support ist gewährleistet.
Das Thema Sicherheit ist für die produktive Nutzung ein entscheidender Aspekt. Denn sobald Sicherheitslücken entdeckt werden, müssen diese so schnell wie möglich geschlossen werden. Da z. B. Docker und Kubernetes mit Go laufen und dies so ziemlich in fast allen Cloud-Diensten eingesetzt werden, sind sehr viele Firmen daran interessiert, auftretende Sicherheitslücken so schnell wie möglich zu beheben. Deswegen sorgt das Go-Team für schnelle Updates und regelmäßige Patches.
Da wir Sicherheitslücken nicht öffentlich als Bug-Report melden sollen, würden wir eine Mail an [email protected] schicken. Der Public Key für eine PGP-Verschlüsselung findet sich auf der Webseite https://golang.org/security. Dort ist auch der gesamte weitere Prozess beschrieben.
ein Jahr Support
Sicherheitsrelevante Updates werden dann für die zwei aktuellsten Go-Versionen veröffentlicht. Der Support für die Version 1.12 endet mit dem Release von 1.14. Der Releasezyklus für Go ist halbjährlich, wodurch die aktuelle Version somit immer ein Jahr lang unterstützt wird.
Warum sollten wir für unseren Editor eine Erweiterung installieren?
Wo finden wir eine Übersicht der Editoren, die Go unterstützen?
Seitdem Go 2009 als Open-Source-Projekt für die Allgemeinheit freigegeben wurde, ist die Popularität der Sprache konstant gestiegen. Im letzten Quatal 2019 lag Go auf Platz 4 aller Pull-Requests von GitHub1. Damit verbunden ist eine immer bessere Unterstützung durch alle gängigen Entwicklungsumgebungen und Editoren.
Einbindung der Werkzeuge
Es gibt sehr gute Erweiterungen für alle gängigen Editoren wie Vim, Emacs, Eclipse oder Sublime. Eine aktuelle Liste aller Editoren mit den dazugehörigen Erweiterungen wird im Go-Wiki unter https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins aufgelistet. Wenn Du also einen bestimmten Editor favorisiert, findest Du dort Informationen bezüglich der Go-Unterstützung. Da Go selbst mit einem großen und mächtigen Werkzeugkasten ausgeliefert wird, ist es sinnvoll, für die Entwicklung in Go auch eine Erweiterung zu benutzen. Das ist beim Coden unglaublich hilfreich. So können wir unseren Code beim Speichern formatieren oder die Importe automatisch ergänzen lassen.
Visual Studio Code
Ich persönlich habe ausgezeichnete Erfahrungen mit Visual Studio Code von Microsoft gemacht. Dieser kostenlose Editor besitzt eine sehr gute Unterstützung für Go und ist für alle Betriebssysteme verfügbar. Seit 2019 gibt es mit gopls auch einen offiziellen Language Server für Go. Durch diesen Server können Editoren die gleichen Funktionalitäten wie Auto-Vervollständigung, Anzeige der Dokumentation oder Springen zur Definition verwenden. Die Unterstützung ist (Stand Ende 2019) noch im experimentellen Stadium und wird laufend ausgebaut.
Was ist Go Playground?
Wofür kann ich Go Playground verwenden?
Wie kann ich Code über Go Playground teilen?
Der Go Playground ist eine Webseite, die es uns ermöglicht, kleine Code-Schnipsel auszuprobieren. Die Seite ist über die URL https://play.golang.org/ erreichbar. Wenn wir die Seite aufrufen, gibt es dort bereits ein paar Zeilen Code, die unserem Hello-World-Programm aus Kapitel 1.7 sehr stark ähneln.
Abb. 1–1Go Playground
Im Playground können wir Code ausführen lassen. Dies ist immer dann hilfreich, wenn wir kleine Sachen schnell und unkompliziert ausprobieren wollen.
Die Seite ist einfach aufgebaut. Oben gibt es die Buttons Run, Format, Imports und Share. Mit Run führen wir den Code aus. Mit Format wird das Tool gofmt ausgeführt, das den Code einheitlich formatiert. Mehr dazu gibt es in Kapitel 4.5. Wenn bei Imports ein Häkchen gesetzt ist, wird unter dem Button Format nicht gofmt, sondern goimports ausgeführt, und es werden zusätzlich alle fehlenden Importe automatisch ergänzt. Denn intern ruft goimports auch den Befehl gofmt auf. Über Share wird der Code gespeichert und eine URL angezeigt. Über diese URL lässt sich der gespeicherte Code wieder aufrufen. Diese Funktion ist hilfreich, wenn wir mal ein Problem haben. So können wir das Problem im Playground nachstellen und dann per Mail, Forum, Stack Overflow oder Slack teilen.
Wie legen wir ein erstes Hello-World-Projekt an?
Wie funktioniert Hello World in Go?
Beginnen wir unsere Reise in die Go-Welt mit Hello, World!. Dafür erstellen wir uns ein neues Verzeichnis hello-world. Anschließend wechseln wir in das neue Verzeichnis. Nun müssen wir unser Projekt initialisieren. Dafür benötigt Go eine weltweit eindeutige Identifikation. Das klingt erst mal kompliziert, ist aber eigentlich immer die URL für das Repository. Also die URL, die wir auch bei git clone verwenden.
go mod init github.com/deinUser/hello-world
go: creating new go.mod: module github.com/deinUser/hello-world
Wenn wir unser Projekt nur lokal verwenden, können wir auch eine fiktive URL wie local/meinModul für die Initialisierung verwenden.
Mit dem init-Befehl initialisieren wir das Go-Projekt auf unserem Rechner. Dabei wird die Datei go.mod angelegt. Dort ist vorerst nur die Definition unseres Projektes enthalten. In Kapitel 6.9 beschäftigen wir uns dann näher mit dem Thema Module und welche Informationen noch in dieser Datei vorhanden sein können. Anschließend erzeugen wir eine Datei mit dem Namen main.go in unserem hello-world-Verzeichnis.
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Gehen wir als Erstes unseren Code Zeile für Zeile durch. Ganz am Anfang geben wir mit package an, zu welchem Paket diese Datei gehört. Wenn wir ein ausführbares Programm erstellen, dann ist der Einstieg immer das Paket main.
Mit import müssen wir alle im Code verwendeten Pakete angeben. Der Go-Compiler ist streng und prüft auch, ob alle aufgeführten Abhängigkeiten im Code verwendet werden. Die Prüfung erfolgt dabei pro Datei. Nicht verwendete Importe erzeugen einen harten Fehler und führen dazu, dass sich das Programm nicht kompilieren lässt.
In Go setzen wir die Importe so gut wie nie selbst. Denn das Programm goimports erledigt diese Aufgabe für uns. Neben den Importen wird dabei auch automatisch der Code einheitlich formatiert. Die meisten Editoren rufen dieses Programm nach jedem Speichern auf. Solltest Du bereits Deine Entwicklungsumgebung für Go aufgesetzt haben, dann kannst Du einfach mal die Importe aus dem Code entfernen und die Datei speichern. Die Importe sollten kurz nach dem Speichern wieder erscheinen.
Namespace der Pakete
Aber gehen wir zurück zu unserem Code. Für die Ausgabe von Hello, World! benötigen wir das fmt-Paket. Durch die Import-Anweisung teilen wir zum einen dem Compiler mit, welche Pakete er noch benötigt, und zum anderen definiert dies in unserem Code einen Namensraum bzw. Namespace. Über diesen Namensraum können wir alle Funktionen, Typen, Variablen und Konstanten des Pakets ansprechen. In unserem Fall wäre das also immer fmt.
Als Nächstes folgt die Definition der Funktion main(). Diese Funktion ist, wie in den meisten anderen Sprachen, der Einstieg in unser Programm. Sie besteht aus fmt.Println("Hello, World!"). Damit rufen wir die Funktion Println() des Pakets fmt auf. Die Funktion selbst sendet den übergebenen String "Hello, World!" an die Standardausgabe.
Um unser Hello-World-Programm auch ausführen zu können, müssen wir es kompilieren. Das machen wir mit dem Befehl go build. Anschließend gibt es in unserem Verzeichnis die ausführbare Datei hello_world bzw. bei Windows hello_world.exe. Wenn wir diese Datei über einen Terminal ausführen, wird der String Hello, World! ausgegeben.
Wenn wir nicht den Umweg über eine kompilierte Datei gehen möchten, können wir unser Programm auch direkt mit go run main.go ausführen. Dies liefert die gleiche Ausgabe wie vorher, nur ohne kompilierte Datei. Streng genommen wird mit go run das Programm auch kompiliert, jedoch wird die Datei in einem temporären Verzeichnis gespeichert und dann ausgeführt. Nachdem das Programm durchgelaufen ist, wird alles wieder gelöscht. Da Go sehr schnell kompiliert, fühlt es sich so an, als ob wir den Code direkt ausführen würden.
Was ist das Besondere an der Go-Spezifikation?
Wo finde ich die Spezifikation?
Bevor eine Programmiersprache tatsächlich implementiert wird, steht die Spezifikation an. Diese beschreibt das Verhalten und die Sprachsyntax. Für Go finden wir die Spezifikation unter https://golang.org/ref/spec. Im Vergleich zu anderen Spezifikationen ist diese verständlich geschrieben und mit Beispielen angereichert. Besonders für Umsteiger aus anderen Sprachen ist es sinnvoll, am Anfang immer wieder einen Blick in dieses Dokument zu werfen.
Effective Go
Auf https://golang.org/ gibt es noch viele weitere sehr hilfreiche und nützliche Ressourcen. Auch die Blog-Einträge sind für jeden Go-Neuling Gold wert. Selbst die älteren Artikel von 2010 sind heute noch relevant. Die Autoren dieser Beiträge sind erfahrene Entwickler des Go-Teams und die Artikel spiegeln auch deren großen Erfahrungsschatz wider. Darauf aufbauend gibt es Effective Go2, das beschreibt, wie wir Go-Code schreiben sollen.
Abb. 1–2Ausschnitt aus der Spezifikation
Wofür verwenden wir das fmt-Paket?
Welche Verben gibt es für
fmt.Printf()
?
Was ist der Unterschied zwischen
fmt.Print()
,
fmt.Fprint()
und
fmt.Sprint()
?
Das fmt-Paket ist uns bereits in Kapitel 1.7, bei der Ausgabe von Hello World begegnet. Der Name des Pakets ist die Kurzform von Format, und wird Fammt ausgesprochen. Die Aufgabe des Pakets ist die formatierte Aus- und Eingabe von Werten. An dieser Stelle wollen wir uns jedoch nur mit den Print-Funktionen beschäftigen, die für die Ausgabe zuständig sind. Denn diese Funktionen werden uns in den kommenden Kapiteln überall wieder begegnen.
Es gibt drei unterschiedliche Print-Funktionen:
Ausgabe in den Standardoutput
Alle Funktionen, die mit der Bezeichnung Print beginnen, schreiben die Werte in den Standardoutput. Das heißt, wenn wir das Programm über die Kommandozeile ausführen, werden die Werte auch dort ausgegeben. Alle Funktionen können beliebig viele Eingabewerte verarbeiten. Dabei gibt fmt.Print() die übergebenen Werte einfach aus. Die Funktion fmt.Println() funktioniert genauso, nur dass am Ende des Outputs automatisch ein Zeilenumbruch ausgegeben wird.
Die Funktion fmt.Printf() ermöglicht uns eine formatierte Ausgabe von Werten. Printf-Funktionen gibt es auch in so ziemlich allen anderen Programmiersprachen. Dabei wird ein Ausgabestring durch sogenannte Verben angereichert. Dem jeweiligen Verb wird dann eine Variable zugeordnet, deren Wert in den String eingefügt wird. Das Tolle daran ist, dass wir über das Verb die Ausgabe formatieren können.
In unserem Beispiel ist der Ausgabestring "%03d: Hallo, %s\n". Mit dem Verb %03d geben wir eine Zahl mit drei Ziffern aus, wobei fehlende Stellen mit 0 gefüllt werden. Das Verb %s steht für die Ausgabe als String.
wichtige Verben
In unseren Beispielen und Projekten werden uns folgende Verben begegnen:
%v
: Die Ausgabe des Wertes einer beliebigen Variablen. Dabei kann jeder Typ über dieses Verb ausgegeben werden.
%+v
: Gibt bei Strukturen zusätzlich die Namen der einzelnen Felder aus.
%#v
: Die Ausgabe erfolgt so, wie wir diese als Go-Code definieren würden.
%T
: Gibt den Typ der Variablen aus.
%b
: Ausgabe zur Basis 2, also als binäre Zahl. Dieses Verb ist nur für Integer zulässig.
%d
: Ausgabe zur Basis 10
%03d
oder
%03b
: Zahlen werden mit
0
aufgefüllt.
%p
: Gibt die Adresse im Speicher aus, also den Pointer der Variablen.
%s
: Direkte Ausgabe eines String
%x
: Ausgabe eines Strings als hexadezimaler Wert
weitere Verben
Das fmt-Paket unterstützt auch noch viele weiteren Verben. Die vollständige Aufstellung aller hier möglichen Ausgabevarianten befindet sich gleich am Anfang der Dokumentation des Pakets unter https://golang.org/pkg/fmt/.
Ausgabe als String
Es kann aber auch sein, dass wir die erzeugten Strings nicht sofort ausgeben möchten. Auch dafür gibt es passende Funktionen in diesem Paket. Es gibt auch die Möglichkeit, die Werte direkt als String zu erhalten. Dafür bekommen die Print-Funktionen das Präfix S. Die Funktionen heißen somit fmt.Sprint(), fmt.Sprintln() und fmt.Sprintf().
In beliebige Datenziele schreiben
Es ist auch möglich, direkt in ein Datenziel zu schreiben. Hierfür verwendet die Standardbibliothek das Interface io.Writer. Dem Thema Interfaces wenden wir uns noch ausführlich in Kapitel 13 widmen. An dieser Stelle ist vorerst nur wichtig, dass wir damit in bestimmte Datenziele direkt schreiben können. So können wir darüber entscheiden, ob wir Daten in eine Datei, als Antwort eines Webservers oder in den Standardoutput schreiben. Diese Funktionen besitzen das Präfix F. Somit sind fmt.Fprint(), fmt.Fprintln() und fmt.Fprintf() die Funktionen, für das Schreiben in bestimmte Datenziele. Als erste Variable wird dabei das Datenziel angegeben.
Das obige Beispiel funktioniert, jedoch ignorieren wir beim Erzeugen der Datei einen möglichen Fehler. Im Kapitel 15 werden wir besprechen, warum das Ignorieren von Fehlern keine gute Idee ist. Da wir hier nur die Funktionsweise von fmt.Fprintf() ausprobieren möchten, wollen wir den Code nicht unnötig aufblähen. Anstatt von file könnten wir als Datenziel hier auch os.Stdout angeben. In diesem Fall würden wir das Ergebnis direkt in den Standardoutput schreiben.
Welche Wörter sind von Go reserviert?
Welche vordefinierten Funktionen gibt es?
Welche Basistypen gibt es?
Als Einstieg in die Sprachsyntax beginnen wir mit den reservierten Wörter und vordefinierten Funktionen. Go wurde von Anfang an als C-ähnliche Sprache designt. Da C viele anderen Programmiersprachen beeinflusst hat, sollten uns die meisten Wörter bekannt vorkommen. Dieser Einstieg erlaubt uns einen ersten Überblick, was die Sprache alles zu bieten hat.
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
Neben den reservierten Wörtern gibt es vordefinierte Funktionen, die direkt in die Sprache integriert sind.
close new panic complex delete
make recover real len append
print imag cap copy println
Go besticht durch Einfachheit.
Vielleicht ist mancher jetzt ein wenig enttäuscht, dass Go gar nicht so viel zu bieten hat. Aber weniger ist in diesem Sinne definitiv mehr. Denn durch diese Einfachheit ist Go leicht zu erlernen. Bei der Konstruktion der Sprache wurde auf alles verzichtet, was nicht unbedingt notwendig ist. Wo es in anderen Sprachen viele unterschiedliche Lösungsmöglichkeiten gibt, ist Go beschränkter und ermöglicht meistens nur einen Weg. Das ist auch den wenigen reservierten Wörtern und Funktionen geschuldet. Es wurde auf alle unnötigen Schnörkel verzichtet. Ein weiterer Aspekt ist die Lesbarkeit des Codes. Weil die wenigen eingebauten Funktionen meistens nur eine Lösung zulassen, sieht Go-Code fast immer gleich aus, unabhängig davon, wer diesen geschrieben hat. Das steigert die Lesbarkeit.
Als Nächstes wollen wir uns die Basistypen ansehen. Auch diese sollten uns bereits aus anderen Sprachen bekannt sein. Go geht auch hier eher den konservativen Weg.
bool string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte rune float32 float64
complex64 complex128
Bezüglich der Operatoren und der Zeichensetzung unterscheidet sich Go auch nicht von anderen Sprachen. Die meisten der in der Spezifikation aufgeführten Einträge dürften uns deshalb bekannt sein.
Das sind die Elemente, die Go als Sprache mitbringt und es uns erlauben, daraus unsere Programme zu erstellen.
Wie können Variablen definiert werden?
Wann sollten wir Variablen als Gruppe definieren?
In Go werden Variablen mit dem Schlüsselwort var definiert. Die Syntax verlangt, dass der Typ immer nach dem Variablennamen angegeben wird. Wenn der Variablen gleich ein Wert zugewiesen wird, kann auch auf den Typ verzichtet werden, da der Wert den Typ bestimmt.
Nullwert
Wenn eine Variable ohne Wert definiert wird, besitzt diese von Anfang an einen initialen Nullwert. Wenn wir später den Wert prüfen, können wir dabei nicht mehr feststellen, ob wir diesen Wert bewusst gesetzt haben oder ob dieser seit der Initialisierung besteht.
var nummer int // 0
var txt string // ""
var checked bool // false
var meinUser *user // nil - Pointer
var liste []string // nil - Slice
Listing 2–6 enthält bereits einen Pointer und ein Slice. Hier ist der Nullwert nil. Darauf werden wir in den Kapiteln 2.4 und 2.12 eingehen.
Variablen können bei der Deklaration gruppiert werden. Diese Gruppierung macht den Code lesbarer, da wir so auf einen Blick sehen, welche Variablen thematisch zusammenpassen, wie das folgende Beispiel aus Effective Go anschaulich zeigt.
Wenn der Variablen gleich ein Wert zugewiesen wird, kann die sogenannte Kurzdeklaration verwendet werden. Hierfür wird vor dem Gleichheitszeichen ein Doppelpunkt gesetzt. Dadurch erfolgt die Definition des Typs der Variablen über den Wert.
nummer := 10 // int
name := getName() // string
In Go können auch mehrere Variablen mit einer Anweisung verarbeitet werden. Hierfür trennen wir die jeweiligen Teile mit einem Komma.
Wir müssen beachten, dass wir einmal deklarierte Variablen im Code auch verwenden müssen. Sogenannte verwaiste Variablen führen sonst zu einem Fehler beim Kompilieren. Besonders am Anfang kann dieser Umstand ganz schön nervig sein. Aber unser Code wird dank dieser Logik nicht durch unnötige Variablen zugemüllt.
Für die Namen unserer Variablen gibt es ein paar Regeln, die wir beachten müssen.
Variablennamen bestehen aus einem Wort, d. h., es darf kein Leerzeichen verwendet werden.
Variablennamen dürfen nur aus Buchstaben, Ziffern und Unterstrichen bestehen.
Variablennamen dürfen nicht mit einer Ziffer beginnen.
öffentliche Variablen
Innerhalb dieser Regeln sind technisch alle Namen zulässig. Wenn wir eigene Pakete erstellen, ist es außerdem entscheidend, ob eine Variable mit einem großen oder einem kleinen Buchstaben beginnt. Denn darüber wird die Sichtbarkeit für andere Pakete gesteuert. Alle Bezeichner in Go, die mit einem Großbuchstaben beginnen, sind öffentlich für andere Pakete. Mehr zu diesem Thema behandelt Kapitel 2.10.
kurz ist besser
Für das Erstellen von Variablen gibt es auch eine Empfehlung: Die Länge der Variablen sollte sich nach deren Verwendung richten. Variablen, die wir nur direkt nach ihrer Deklaration verwenden oder eine begrenzte Gültigkeit haben, sollten kurz sein. Klassisch ist hier der Index. Wir schreiben lieber i anstatt index.
for i := 0; i < 10; i++ {
fmt.Println(i)
}
Camel Case
Wenn Variablen über längere Abschnitte im Code verwendet werden, dann verwenden wir natürlich einen sprechenderen Namen. Dieser kann auch aus mehreren umschreibenden Wörtern bestehen, die wir mittels Camel Case zusammenfügen.
var userName string // idiomatischer Stil
var user_name string // schlechter Stil
Bei Abkürzungen im Namen sollten diese einheitlich komplett großgeschrieben sein.
var userURL string
var HTTPServer server // exportierte Variable
var httpServer server // nicht exportierte Variable
Die Ausnahme von der Regel sind nicht öffentliche Variablen, die mit einem Kleinbuchstaben beginnen müssen. In diesem Fall schreiben wir die komplette Abkürzung klein.
Was sind untypisierte Konstanten?
Wie deklarieren wir typisierte Konstanten?
Was ist iota?
Konstanten werden wie Variablen deklariert, jedoch verwenden wir dafür das Schlüsselwort const. Die Kurzdeklaration mit := ist bei Konstanten nicht möglich. Konstanten können auch keine Strukturen (Kapitel 2.7) aufnehmen. Sobald wir einmal eine Konstante deklariert haben, können wir diese nicht mehr ändern.
Eine Besonderheit von Konstanten ist die Typbehandlung. Denn Konstanten sind in der Regel untypisiert. Das heißt, dass bei der Verwendung der Konstante versucht wird, diese in den benötigten Typ umzuwandeln. Sollte eine Typumwandlung nicht möglich sein, erzeugt dies einen Fehler beim Kompilieren.
Folgendes Beispiel zeigt, dass wir eine Konstante, die als Fließkommazahl deklariert wurde, auch als Integer verwenden können. Wenn wir statt einer Konstanten eine Variable verwenden, kommt es zu einem Fehler.
Wir können eine Konstante natürlich auch typisiert deklarieren. Hierfür müssen wir den Typ einfach bei der Deklaration mitgeben. Auch die Funktion der Typumwandlung (Kapitel 2.6) ist dabei zulässig.
Bei numerischen Konstanten können wir diese mit einem automatischen Zähler definieren. Hierfür benötigen wir das Schlüsselwort iota. Der Zähler beginnt bei jeder neuen Gruppe von Konstanten bei Null und zählt mit jedem Aufruf um eins hoch. Die Zuweisung von iota ist nur für die erste Konstante notwendig. Für die folgenden Konstanten können wir auch auf die Zuweisung verzichten.
Der Zähler zählt immer nur bei einem Aufruf hoch. Das heißt Kommentare oder Leerzeilen werden ignoriert. Wenn wir diesen gezielt um einen Wert hoch setzen möchten, benötigen wir dafür den Blank Identifier _ (Kapitel 2.19).
Die Ausdrücke innerhalb der Deklaration mit iota können auch komplexer sein, wie das folgende Beispiel aus Effective Go zeigt.
Wir verwenden hier den Zähler in Verbindung mit einem Left Shift. Hierüber können wir einfach die korrekte Byte-Größe für KB, MB, GB usw. berechnen.
Wie werden Pointer in Go geschrieben?
Welches Zeichen wird für die Dereferenzierung verwendet?
Wie bei anderen Sprachen können in Go Variablen auch Zeiger auf bestimmte Bereiche im Arbeitsspeicher sein. Diese Pointer beinhalten selbst keinen Wert, sondern zeigen nur auf eine andere Variable.
Abb. 2–1Der Gopher zeigt auf eine Adresse des Arbeitsspeichers.
Wenn wir als Typ einen Pointer definieren, dann verwenden wir *. Ein Pointer auf int wird somit als *int geschrieben. Wenn wir von einer bestehenden Variablen die Speicheradresse auslesen, dann setzen wir & vor die Variable.
In diesem Beispiel wird deutlich, wie einfach ein Pointer zu erzeugen ist. Wenn wir den Wert von b ausgeben, erhalten wir somit auch die Adresse im Arbeitsspeicher. Wenn wir den Wert benötigen oder verändern möchten, müssen wir die Variable dereferenzieren. Dies erfolgt wieder mit dem vorangestellen *. Wenn wir den Wert eines Pointers verändern, ändert sich der Wert im Arbeitsspeicher. In unserem Beispiel ist dies der Speicher der Variable a. Deshalb können wir über den Pointer auch den Wert von a ändern.
In Go ist es ebenfalls möglich, dass eine Funktion nicht den Wert einer Variablen, sondern einen Pointer zurückmeldet.
func foo() *int {
bar := 123
return &bar
}
func main() {
a := foo()
fmt.Println(a, *a)
}
Die Variable bar ist innerhalb der Funktion foo() gültig. Normalerweise werden lokale Variablen nach dem Funktionsaufruf innerhalb des Arbeitsspeichers wieder gelöscht. Da wir diesen Bereich des Arbeitsspeichers innerhalb von main() weiter verwenden, bleibt dieser Wert bestehen, solange er benötigt wird.
Wie werden eigene Typen definiert?
Was sind die Vorteile von eigenen Typen?
Die durch Go bereitgestellten Basistypen bilden das Grundgerüst. Damit lässt sich schon einiges umsetzen. Wir können natürlich auch eigene Typen definieren und dadurch unseren Programmen weiteren Kontext mitgeben. Schauen wir uns dazu ein einfaches Beispiel an.
func MeterToZentimeter(m int) int{
return m * 100
}
Die Funktion MeterToZentimeter rechnet eine Länge in Meter nach Zentimeter um. Als Datentyp verwendet sie int. Das ist technisch korrekt. Wir könnten hier aber dem Ganzen mehr Kontext geben, wenn wir für Meter und Zentimeter eigene Typen definieren würden.
type meter int
type zentimeter int
Eigene Typen werden dabei mit type definiert, danach folgt der Name des Typs und dann der dazugehörige Basistyp.
Wie schon bei den Variablen ist es möglich, auch Typdefinitionen zu gruppieren.
type (
meter int
zentimeter int
)
Jetzt ändern wir unsere Funktion so, dass diese mit den unterschiedlichen Typen meter und zentimeter funktioniert.
func MeterToZentimeter(m meter) zentimeter {
return zentimeter(m * 100)
}
Wenn wir jetzt unsere Funktion nutzen wollen, müssen wir als Input immer meter verwenden und erhalten als Output immer zentimeter. Der Compiler ist an dieser Stelle sehr streng. Innerhalb der Return-Anweisung benötigen wir für die Umrechnung eine Typumwandlung, die wir in Kapitel 2.6 ausführlich betrachten werden.
Wie können wir einen Typ in Go umwandeln?
Wann ist eine Umwandlung möglich?
Wenn wir in unserem Code Variablen unterschiedlichen Typs verarbeiten, wirft der Compiler einen Fehler. Denn wegen der strengen Typisierung dürfen wir keine Typen mischen. Wir können nicht Äpfel mit Birnen vergleichen. Wenn wir in Go versuchen, Äpfel und Birnen zu addieren, bekommen wir vom Compiler eins auf die Mütze.
func main() {
type äpfel int
type birnen int
a := äpfel(10)
b := birnen(5)
fmt.Println(a + b)
}
Output:
// invalid operation: a + b (mismatched types äpfel and birnen)
Ein direkter Vergleich von Äpfeln und Birnen ist also nicht zulässig. Wenn wir aber z. B. nur die Anzahl an Obst berechnen wollen, dann müssen wir unsere Variablen umwandeln. Hierfür verwenden wir die eingebaute Typumwandlung. Da wir sowohl äpfel als auch birnen als Basistyp int verwenden, sollte die Umwandlung problemlos klappen.
Für eine Typumwandlung gibt es zu jedem Typ eine Funktion mit dem Namen des Typs. Das gilt sowohl für Basistypen als auch für eigene Typen.
func main() {
type äpfel int
type birnen int
a := äpfel(10)
b := birnen(5)
anzahl := int(a) + int(b)
fmt.Printf("Anzahl Früchte: %d", anzahl)
}
Für unser Beispiel wandeln wir a und b mit int() einfach in deren Basistypen um.
Ob eine Typumwandlung möglich ist, hängt von unterschiedlichen Faktoren ab. Laut Spezifikation kann der Wert x als Typ T in folgenden Fällen umgewandelt werden:
Der Typ von
x
ist gleich
T
.
Der Typ von
x
und der von
T
besitzten den gleichen Basistyp.
x
ist ein Integer-Wert, ein
rune
oder ein
[]byte
und
T
ein String.
x
ist ein String und
T
ein
rune
oder ein
[]byte
.
Wenn wir eine nicht mögliche Umwandlung im Code durchführen möchten, kommt es zu einem Fehler beim Kompilieren.
Wie wird eine Struktur definiert?
Was ist eine anonyme Struktur?
Was sind
struct
-Tags?
Strukturen sind zusammengesetzte Typen. Das heißt, wir können mehrere Typen in einer Struktur zu einem neuen Typ zusammensetzen. Die Abbildung ist dabei so wie in C oder C++. Mit dem Schlüsselwort struct werden Strukturen definiert. Dabei können wir mehrere Felder definieren. Es lassen sich dafür alle in Go definierten Typen ohne Einschränkung verwenden. Bei der Definition der Struktur wird zuerst der Name des Feldes und dann der Typ angegeben.
type adresse struct {
strasse string
stadt string
}
Wenn wir Variablen deklarieren, dann verwenden wir sogenannte Composite Literals. Diese beginnen mit dem Typnamen der Struktur. Danach werden die Werte innerhalb eines Blocks angegeben.
// Deklaration mit Feldnamen
a := adresse{
strasse: "Musterstr.",
stadt: "Musterstadt",
}
// Deklaration mit allen Feldern
b := adresse{"Bahnhofstr.", "Berlin"}
Bei Variable a haben wir die Werte direkt über die Feldnamen zugeordnet. Bei Variable b wurde auf die Namen verzichtet. Dies ist bei kleinen Strukturen in Ordnung. Denn bei dieser Art der Deklaration müssen wir alle Felder der Struktur in der korrekten Reihenfolge befüllen.
Wir können auch Strukturen aus anderen Strukturen zusammenstellen. Dieses Vorgehen wird in der objektorientierten Programmierung als Komposition bezeichnet. Wenn wir einen User definieren, können wir dort einfach Adressen einbinden.
type user struct {
name string
adresse1 adresse
adresse2 adresse
}
Wenn wir den Typ adresse nur einmal einbinden, dann müssen wir gar keinen eigenen Namen vergeben. Deshalb bezeichnen wir dieses auch als anonymes Feld.
type user struct {
name string
adresse
}
Das anonyme Feld bekommt den Typnamen als Bezeichner automatisch zugewiesen. Wir können somit bei der Deklaration auch anonyme Felder ansprechen.
Abb. 2–2Zusammengesetzte Struktur
a := user{
name: "Max",
adresse: adresse{
strasse: "Musterstrasse",
stadt: "Musterstadt",
},
}
anonyme Struktur
Strukturen können auch direkt bei der Zuweisung einer Variablen definiert werden. Da wir dafür keinen eigenen Typ definieren, werden diese als anonyme Strukturen bezeichnet. Sinnvoll ist dies natürlich nur dann, wenn wir die Struktur auch nur einmal benötigen. In der Praxis ist das bei Tests, Templates oder dem Erzeugen von anderen externen Formaten wie JSON der Fall.
testfall := struct {
Input string
Expect string
}{
"foo",
"bar",
}
Die Variable testfall besitzt hier keinen konkreten Typ. Die anonyme Struktur definiert die Felder für die Variable. Anschließend müssen wir auch gleich die Werte über einen eigenen Block definieren. Die Darstellung mag am Anfang vielleicht ein wenig ungewohnt sein, jedoch wird uns dieses Konstrukt noch öfters in diesem Buch bei den automatisierten Tests unserer Projekte begegnen.
Zu jedem Feld können wir auch zusätzliche Informationen definieren. Diese Informationen werden hinter die Typdefinition des jeweiligen Feldes geschrieben. Dieses Konstrukt wird auch Tags genannt. Wir können also den Feldern unserer Adresse noch weitere Metadaten hinzufügen.
type Adresse struct {
Strasse string `json:"street"`
Stadt string `json:"city"`
}
Mit den struct-Tags haben wir die Möglichkeit für den JSON-Export andere Feldnamen zu verwenden. In unserem Beispiel haben wir die Felder übersetzt und dabei sowohl den Typ als auch die Felder großgeschrieben. Damit haben wir sowohl Typ als auch Feld public gemacht. Ausführlich ist dieses Konzept in Kapitel 2.10 beschrieben.
Tags werden immer dann verwendet, wenn die Felder zusätzliche Informationen benötigen. Das ist meistens dann der Fall, wenn wir Daten umwandeln. In Kapitel 14 werden wir später noch selbst Struct-Tags auslesen.
Wie werden Funktionen definiert?
Wie werden anonyme Funktionen definiert?
Wofür wird
defer
verwendet?
Für die Definition von Funktionen wir das Schlüsselwort func verwendet. Eine typische Definition sieht wie folgt aus:
func greet(name string) string {
return fmt.Sprintf("Hallo %s!", name)
}
func main() {
greeting := greet("Alice")
fmt.Println(greeting) // Output: Hallo Alice!
}
// Output: Hallo Alice!