Go Home Page
Die Programmiersprache Go

The Go Programming Language Specification — Deutsche Übersetzung

Das Original:
https://golang.org/doc/go_spec.html
Version of June 13, 2018 (go1.11)
Diese Übersetzung:
bitloeffel.de/DOC/golang/go_spec_20180826_de.html
Stand: 29.06.2018
© 2012-18 Hans-Werner Heinzen @ Bitloeffel.de
Die Nutzung dieses Werks ist unter den Bedingungen der "Creative Commons Attribution 3.0"-Lizenz erlaubt.
Für Fachbegriffe und ähnliches gibt es hier noch eine Wörterliste.
Achtung:
Dieses Dokument ist nicht die offizielle Sprachreferenz. Es ist eine Übersetzung, und deshalb — wie jede Übersetzung — nur "quasi dasselbe mit anderen Worten". Maßgebend ist das Original.

Go-Sprachbeschreibung

Einleitung

Dies ist ein Referenzhandbuch für die Programmiersprache Go [aber: siehe Bemerkung im Kasten oben, A.d.Ü.]. Weitere Informationen und Dokumente finden Sie unter https://golang.org.

Go ist eine universelle Programmiersprache, die ursprünglich als Sprache für Systementwicklung entwickelt wurde. Sie hat strenge Typprüfung sowie eine Automatische Speicherbereinigung [englisch: garbage collector, A.d.Ü], und unterstützt explizit das Programmieren von Nebenläufigkeit. Programme werden aus Paketen zusammengebaut, die so konstruiert sind, dass Abhängigkeiten effizient gehandhabt werden können.

Die Grammatik ist kompakt und regulär, so dass sie von automatischen Werkzeugen, wie z.B. Integrierten Entwicklungsumgebungen, leicht zu analysieren ist.

Schreibweise

Die Syntax wird in der Erweiterten Backus-Naur Form (EBNF) beschrieben:

Definition   = definiens_name "=" [ Ausdruck ] "." .
Ausdruck     = Alternative { "|" Alternative } .
Alternative  = Term { Term } .
Term         = definiens_name | vokabel [ "…" vokabel ] | Gruppierung | Option | Wiederholung .
Gruppierung  = "(" Ausdruck ")" .
Option       = "[" Ausdruck "]" .
Wiederholung = "{" Ausdruck "}" .

Definitionen werden also aus Termen und den folgenden Operatoren gebildet (aufsteigend sortiert nach Vorrang):

|   Auswahl
()  Gruppierung
[]  Option (0 oder 1 mal)
{}  Wiederholung (0 bis n mal)

Kleingeschriebene definiens_namen stehen für lexikalische Sinneinheiten. Nicht-Terminale dagegen werden mit Binnenmajuskeln ("CamelCase") geschrieben. Lexikalische Sinneinheiten selbst werden durch Anführungszeichen "" oder Gravis-Zeichen `` eingeschlossen.

Die Schreibweise a … b steht für die Menge der Zeichen von a bis b, aus der ausgewählt werden kann. Das Auslassungszeichen wird auch an anderer Stelle dieser Sprachbeschreibung benutzt, um Aufzählungen oder nicht weiter beschriebene Kode-Schnipsel zu markieren. Das Zeichen ist kein Bestandteil der Sprache Go. (Anders ist das mit der Drei-Zeichen-Folge ...)

Quellkode

Quellkode ist UTF-8-kodierter Unicode-Text. Der Text ist nicht normalisiert, was bedeutet, dass eine einzelne Kodenummer für einen Akzent-Buchstaben sich unterscheidet von demselben Akzent-Buchstaben, der aus einem Akzent und einem Buchstaben kombiniert wurde; diese werden als zwei Kodenummern behandelt. Wir benutzen in diesem Dokument den ungenauen Begriff Zeichen und meinen damit eine Unicode-Kodenummer im Quelltext.

Jede Kodenummer ist eindeutig; zum Beispiel sind Klein- und Großbuchstaben voneinander verschiedene Zeichen.

Go-Implementierungseinschränkung: Um mit anderen Werkzeugen kompatibel zu bleiben, darf der Compiler das Zeichen NUL (U+0000) im Quellkode verbieten.

Go-Implementierungseinschränkung: Um mit anderen Werkzeugen kompatibel zu bleiben, darf der Compiler die UTF-8-kodierte Bytereihenfolgemarke (U+FEFF) im Quellkode ignorieren, wenn sie die erste Unicode-Kodenummer im Quelltext ist. Die Bytereihenfolgemarke kann für den gesamten Quellkode verboten werden.

Zeichen

Die folgenden Terme stehen für bestimmte Klassen von Unicode-Zeichen:

newline        = /* die Unicode-Kodenummer U+000A */ .
unicode_char   = /* jede beliebige Unicode-Kodenummer ausgenommen newline */ .
unicode_letter = /* eine Unicode-Kodenummer der Kategorie "Letter" */ .
unicode_digit  = /* eine Unicode-Kodenummer der Kategorie "Number, decimal digit" */ .

"The Unicode Standard 8.0", Section 4.5 "General Category" definiert eine Reihe von Zeichen-Kategorien. Go behandelt alle Zeichen der Buchstaben-Kategorien Lu, Ll, Lt, Lm und Lo als Unicode-Buchstaben, und die der Zahlen-Kategorie Nd als Unicode-Ziffern.

Buchstaben und Ziffern

Der Unterstrich _ (U+005F) wird als Buchstabe angesehen,

letter        = unicode_letter | "_" .
decimal_digit = "0" … "9" .
octal_digit   = "0" … "7" .
hex_digit     = "0" … "9" | "A" … "F" | "a" … "f" .

Lexikalische Bestandteile

Kommentare

Kommentare dienen der Programmdokumentation. Es gibt zwei Formen davon:

  1. Zeilenkommentare beginnen mit der Zeichenfolge // und enden am Ende der Zeile.
  2. Allgemeine Kommentare beginnen mit der Zeichenfolge /* und enden mit dem ersten Auftreten der Zeichenfolge */.

Ein Kommentar kann nicht innerhalb einer Rune oder eines String-Literals oder eines Kommentars beginnen. Ein allgemeiner Kommentar ohne Zeilenvorschub wirkt wie ein Leerzeichen. Jeder andere Kommentar wirkt wie ein Zeilenvorschub.

Vokabeln

Vokabeln [englisch: tokens, A.d.Ü.] bilden das Vokabular der Sprache Go. Es gibt vier Klassen: Bezeichner, Schlüsselwörter, Operatoren und Satzzeichen, und Literale. Leerraum [englisch: white space, A.d.Ü.] kann bestehen aus Leerzeichen (U+0020), Tabulatorzeichen (U+0009), Wagenrückläufen (U+000D) und Zeilenvorschüben (U+000A), und wird ignoriert. Er dient nur als Trennzeichen zwischen Vokabeln, die sonst als eine kombinierte Vokabel gelesen würden. Darüberhinaus können Zeilenvorschub oder Dateiende das Einfügen eines Semikolons auslösen. Beim Aufbrechen einer Eingabe in Vokabeln ist die längste Zeichenfolge, die eine gültige Vokabel darstellt, die nächste Vokabel.

Semikolon

Die formale Grammatik benutzt Semikolons ";" als Endezeichen für eine Reihe von Definitionen. Go-Programme können die meisten dieser Semikolons weglassen, da die beiden folgenden Regeln angewandt werden:

  1. Beim Aufbrechen des Eingabestroms in Vokabeln wird unmittelbar nach der letzten Vokabel der Zeile automatisch ein Semikolon in den Vokabel-Strom eingefügt, wenn die letzte Vokabel der Zeile

    ist.

  2. Damit komplexe Anweisungen in einer einzelnen Zeile möglich werden, kann ein Semikolon vor einem schließenden ")" or "}" weggelassen werden.

Um typischen Kode zu zeigen, werden in den Beispielen dieses Dokuments Semikolons gemäß den genannten Regeln weggelassen.

Bezeichner

Bezeichner benennen Programmbestandteile wie etwa Variablen oder Typen. Ein Bezeichner ist eine Folge von einem oder mehreren Buchstaben und Ziffern. Das erste Zeichen eines Bezeichners muss ein Buchstabe sein.

identifier = letter { letter | unicode_digit } .
a
_x9
DieseVariableWirdExportiert
αβ

Einige Bezeichner sind vordeklariert.

Schlüsselwörter

Die folgenden Schlüsselwörter sind reserviert, und dürfen nicht als Bezeichner verwendet werden:

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

Operatoren und Satzzeichen

Die folgenden Zeichenfolgen stehen für Operatoren, (den Zuweisungsoperatot mit eingeschlossen) und Satzzeichen:

+    &     +=    &=     &&    ==    !=    (    )
-    |     -=    |=     ||    <     <=    [    ]
*    ^     *=    ^=     <-    >     >=    {    }
/    <<    /=    <<=    ++    =     :=    ,    ;
%    >>    %=    >>=    --    !     ...   .    :
     &^          &^=

Ganzzahlliterale

Ein Ganzzahlliteral ist eine Folge von mehreren Ziffern, die für eine Ganzzahlkonstante stehen. Ein optionaler Präfix legt eine nicht-dezimale Basiszahl fest: 0 für Oktalzahlen, 0x oder 0X für Hexadezimalzahlen. In Hexadezimalliteralen stehen a-f und A-F für die Werte 10 bis 15.

int_lit     = decimal_lit | octal_lit | hex_lit .
decimal_lit = ( "1" … "9" ) { decimal_digit } .
octal_lit   = "0" { octal_digit } .
hex_lit     = "0" ( "x" | "X" ) hex_digit { hex_digit } .
42
0600
0xBadFace
170141183460469231731687303715884105727

Gleitkommaliterale

Ein Gleitkommaliteral ist eine dezimale Darstellung einer Gleitkommakonstanten. Es hat einen ganzzahligen und einen gebrochenen Teil und einen Exponenten. Der ganzzahlige und der gebrochene Teil bestehen aus Dezimalziffern; der Exponententeil setzt sich zusammen aus e oder E gefolgt von einem dezimalen Exponenten, dieser eventuell mit Vorzeichen. Entweder der ganzzahlige oder der gebrochene Teil darf fehlen; entweder der Dezimalpunkt oder der Exponent darf fehlen.

float_lit = decimals "." [ decimals ] [ exponent ] |
            decimals exponent |
            "." decimals [ exponent ] .
decimals  = decimal_digit { decimal_digit } .
exponent  = ( "e" | "E" ) [ "+" | "-" ] decimals .
0.
72.40
072.40  // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5

Imaginärliterale

Ein Imaginärliteral ist eine dezimale Darstellung des imaginären Teils einer Komplexkonstante. Es besteht aus einem Gleitkommaliteral oder einer dezimalen Ganzzahl, gefolgt vom kleingeschriebenen Buchstaben i.

imaginary_lit = (decimals | float_lit) "i" .
0i
011i  // == 11i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i

Runenliterale

Ein Runenliteral steht für eine Runenkonstante; das ist ein Ganzzahlwert, der für eine Unicode-Kodenummer steht. Ein Runenliteral wird dargestellt durch ein oder mehrerer Zeichen eingeschlossen in Hochkommata, wie in 'x' oder '\n'. Innerhalb der Hochkommata sind alle Zeichen erlaubt außer Zeilenvorschub oder Hochkomma ohne Fluchtsymbol. Ein einzelnes Zeichen in Hochkommata steht für den Unicode-Wert des Zeichens selbst, wohingegen Zeichenfolgen, die mit Rückschräger beginnen, Werte in ganz verschiedenen Formaten darstellen.

Die einfachste Form ist ein einzelnes Zeichen in Hochkommata; da Go-Quellkode aus UTF-8-kodierten Unicode-Zeichen besteht, können mehrere UTF-8-kodierte Bytes für einen Ganzzahlwert stehen. Zum Beispiel enthält das Literal 'a' ein Byte, das für das Zeichen a oder Unicode U+0061 oder den Wert 0x61 steht, wohingegen 'ä' 2 Bytes enthält (0xc3 0xa4), die für den Umlaut ä oder U+00E4 oder den Wert 0xe4 stehen.

Verschiedene Formate mit dem Rückschräger als Fluchtsymbol erlauben es, beliebige Werte als ASCII-Text zu kodieren. Ganzzahlige Werte können auf 4 verschiedene Weisen als numerische Konstanten dargestellt werden: durch \x gefolgt von genau zwei hexadezimalen Ziffern, durch \u gefolgt von genau vier hexadezimalen Ziffern, durch \U gefolgt von genau acht hexadezimalen Ziffern und durch einen einfachen Rückschräger \ gefolgt von genau drei Oktalziffern. In all diesen Fällen ist der Wert des Literals der Wert, für den die Ziffern im jeweiligen Zahlensystem stehen.

Obwohl jede dieser Darstellungen zu einer Ganzzahl führt, haben sie verschiedene Gültigkeitsbereiche. Die Oktaldarstellung muss für einen Wert zwischen 0 und 255 (inklusive) stehen. Die Darstellungen mit \u und \U stehen für Unicode-Kodenummern, für die einige Werte nicht erlaubt sind, insbesondere jene oberhalb von 0x10FFFF sowie die Hälften von Ersatzkodierungen.

Einige wenige Zeichen alleine hinter einem Rückschräger stehen für besondere Werte:

\a   U+0007 Alarm
\b   U+0008 Rückwärtslöschen
\f   U+000C Seitenvorschub
\n   U+000A Zeilenvorschub
\r   U+000D Wagenrücklauf
\t   U+0009 horizontaler Tabulator
\v   U+000b vertikaler Tabulator
\\   U+005c Rückschräger
\'   U+0027 Hochkomma          (nur gültig innerhalb eines Runenliterals)
\"   U+0022 Anführungszeichen  (nur gültig innerhalb eines String-Literals)

Alle anderen 2-Zeichen-Folgen sind innerhalb von Runenliteralen nicht erlaubt.

rune_lit         = "'" ( unicode_value | byte_value ) "'" .
unicode_value    = unicode_char | little_u_value | big_u_value | escaped_char .
byte_value       = octal_byte_value | hex_byte_value .
octal_byte_value = `\` octal_digit octal_digit octal_digit .
hex_byte_value   = `\` "x" hex_digit hex_digit .
little_u_value   = `\` "u" hex_digit hex_digit hex_digit hex_digit .
big_u_value      = `\` "U" hex_digit hex_digit hex_digit hex_digit
                           hex_digit hex_digit hex_digit hex_digit .
escaped_char     = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a'
'ä'
'本'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'
'\''          // Runenliteral mit einem einzelnen Hochkomma
'aa'          // verboten: zu viele Zeichen
'\xa'         // verboten: zu wenige Hexadezimalziffern
'\0'          // verboten: zu wenige Oktalziffern
'\uDFFF'      // verboten: Hälfte einer Ersatzkodierung
'\U00110000'  // verboten: ungültige Unicode-Kodenummer

String-Literale

Ein String-Literal steht für eine String-Konstante, die man erhält, wenn man eine Folge von Zeichen verkettet. Es gibt 2 Formen: rohe und interpretierte String-Literale.

Rohe String-Literale sind Zeichenfolgen zwischen Gravis-Zeichen, wie in `foo`. Innerhalb dieser ist jedes Zeichen erlaubt — außer dem Gravis-Zeichen. Der Wert eines rohen String-Literals ist die Verkettung der uninterpretierten (implizit UTF-8-kodierte) Zeichen zwischen den Gravis-Zeichen; insbesondere haben Rückschräger hier keine besondere Bedeutung und der String darf Zeilenvorschübe enthalten. Wagenrücklaufzeichen ('\r') innerhalb roher String-Literale werden verworfen.

Interpretierte String-Literale sind Zeichenfolgen zwischen Anführungszeichen, wie in "bar". Inerhalb dieser ist jedes Zeichen erlaubt — außer Zeilenvorschub und Anführungszeichen ohne Fluchtsymbol. Der Text zwischen diesen Zeichen bildet den Wert des Literals, wobei Sequenzen mit dem Rückschräger als Fluchtsymbol so interpretiert werden, wie bei den Runenliteralen auch, mit denselben Einschränkungen. (Ausnahme: \' ist nicht erlaubt, \" dagegen schon.) Die drei-ziffrige Oktalform (\nnn) und die zwei-ziffrige Hexadezimalform (\xnn) stehen für einzelne Bytes; die anderen Formen stehen für die (möglicherweise Multi-Byte-) UTF-8-Kodierung eines einzelnen Zeichens. Also stehen innerhalb eines interpretierten String-Literals \377 und \xFF für ein einzelnes Byte mit dem Wert 0xFF=255, während ÿ, \u00FF, \U000000FF und \xc3\xbf für die zwei Bytes 0xc3 0xbf der UTF-8-Kodierung des Zeichens U+00FF stehen.

string_lit             = raw_string_lit | interpreted_string_lit .
raw_string_lit         = "`" { unicode_char | newline } "`" .
interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc`                 // gleich "abc"
`\n
\n`                   // gleich "\\n\n\\n"
"\n"
"\""                  // gleich `"`
"Hallo, Welt!\n"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"
"\uD800"              // verboten: Hälfte einer Ersatzkodierung
"\U00110000"          // verboten: ungültige Unicode-Kodenummer

Die folgenden Beispiele stehen alle für den gleichen String:

"日本語"                                 // UTF-8-Eingabetext
`日本語`                                 // UTF-8-Eingabetext als rohes Literal
"\u65e5\u672c\u8a9e"                    // die expliziten Unicode-Kodenummern
"\U000065e5\U0000672c\U00008a9e"        // die expliziten Unicode-Kodenummern
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"  // die expliziten UTF-8-Bytes

Wenn der Quellkode ein Zeichen mit zwei Kodenummern enthält, sagen wir ein kombiniertes Zeichen aus Akzent und Buchstabe, dann führt es zu einem Fehler, wenn das in einem Runenliteral versucht wird, und es ergibt zwei Zeichen in einem String-Literal.

Konstanten

Es gibt Boole'sche Konstanten, Runenkonstanten, Ganzzahlkonstanten, Gleitkommakonstanten, Komplexkonstanten und String-Konstanten. Runen-, Ganzzahl-, Gleitkomma- und Komplexkonstanten nennt man numerische Konstanten.

Der Wert einer Konstanten wird dargestellt durch ein Runen-, ein Ganzzahl-, ein Gleitkomma-, ein Imaginär- oder ein String-Literal, einen Bezeichner, der eine Konstante benennt, einen konstanten Ausdruck, eine Konversion mit einer Konstanten als Ergebnis, sowie das Ergebnis der Anwendung einer Standardfunktion wie unsafe.Sizeof auf einen beliebigen Wert, wie cap oder len auf bestimmte Ausdrücke, wie real und imag auf eine Komplexkonstante und wie complex auf numerische Konstanten. Die Boole'schen Wahrheitswerte werden repräsentiert durch die vordeklarierten Konstanten true und false. Der vordeklarierte Bezeichner iota benennt eine Ganzzahlkonstante.

Komplexkonstanten sind eine Form von konstanten Ausdrücken und werden im Abschnitt mit dieser Überschrift abgehandelt.

Numerische Konstanten stehen für exakte Werte beliebiger Genauigkeit und kennen keinen Überlauf. Folgerichtig gibt es keine Konstanten für die durch IEEE-754 definierten negative Null, unendlich oder NaN (keine Zahl).

Konstanten können typbehaftet oder typfrei sein. Konstanten in Form von Literalen sowie true, false, iota, und bestimmte konstante Ausdrücke, die nur typfreie konstante Operanden enthalten, sind typfrei.

Einer Konstanten kann explizit ein Typ zugeordnet werden durch eine Konstantendeklaration oder eine Konversion, implizit innerhalb einer Variablendeklaration oder in einer Zuweisung oder als einem Operanden in einem Ausdruck.

Es ist ein Fehler, wenn der Wert einer Konstanten nicht als Wert des zugewiesenen Typs dargestellt werden kann.

Beispielsweise kann der Konstanten 3.0 jeder Ganzzahl- und jeder Gleitkommatyp zugeordnet werden. Dagegen kann der Konstanten 2147483648.0 (gleich 1<<31) zwar der Typ float32, float64 oder uint32 zugeordnet werden, aber nicht int32 oder string.

Zu einer typfreien Konstanten gehört ein Standardtyp, nämlich der Typ, in den die Konstante implizit konvertiert wird, sobald ein typbehafteter Wert gebraucht wird, zum Beispiel in einer Variablenkurzdeklaration wie i := 0, die keinen expliziten Typ enthält. Die Standardtypen für typfreie Konstanten sind bool, rune, int, float64, complex128 oder string, abhängig davon, ob es sich um Boole'sche, Runen-, Ganzzahl-, Gleitkomma-, Komplex- oder String-Konstante handelt.

Go-Implementierungseinschränkung: Wenn auch die Sprache numerische Konstanten beliebiger Genauigkeit vorsieht, so mag ein Compiler sie intern mit eingeschränkter Genauigkeit implementieren. Doch jede Go-Implementierung muss:

Diese Bedingungen gelten sowohl für Konstantenliterale als auch für Ergebnisse der Auswertung von konstanten Ausdrücken.

Variablen

Eine Variable ist ein Speicherort für einen Wert. Die Menge seiner erlaubten Werte wird festgelegt durch den Typ der Variablen.

Eine Variablendeklaration oder, im Fall von Funktionsparametern und Rückgabewerten, die Signatur einer Funktionsdeklaration oder eines Funktionsliterals reserviert Speicher für eine namensbehaftete Variable. Aufruf der Standardfunktion new oder das Abgreifen der Adresse eines Verbundliterals teilt zur Laufzeit Speicher für diese Variable zu. Solch eine anonyme Variable wird durch eine (möglicherweise implizite) Zeigerauflösung angesprochen.

Strukturierte Variablen vom Typ Array, Slice oder Struct haben Elemente oder Felder, die einzeln angesprochen werden können. Ein solches Element verhält sich wie eine Variable.

Der statische Typ (oder nur Typ) einer Variablen ist der Typ, der in der Deklaration oder beim Aufruf von new oder im Verbundliteral angegeben wurde, oder der Typ des Elements einer strukturierten Variablen. Variablen eines Interface-Typs haben außerdem einen davon unterschiedenen dynamischen Typ, welcher der aktuelle Typ des Wertes zur Laufzeit ist (solange der Wert nicht der vordeklarierte Bezeichner nil ist, der typfrei ist). Der dynamische Typ kann sich während der Ausführung ändern, doch die Werte, die in Interface-Variablen gespeichert sind, sind immer dem statischen Typ der Variablen zuweisbar.

var x interface{}  // x ist nil und hat den statischen Typ interface{}
var v *T           // v hat den Wert nil und den statischen Typ *T
x = 42             // x hat den Wert 42 und den dynamischen Typ int
x = v              // x hat den Wert (*T)(nil) und den dynamischen Typ *T

Den Wert einer Variablen erhält man, indem man sie in einem Ausdruck anspricht; es ist dann der zuletzt der Variablen zugewiesene Wert. Wenn der variablen bis dahin noch kein Wert zugewiesen worden ist, ist ihr Wert der Nullwert ihres Typs.

Typen

Ein Typ legt einen Menge von Werten und Operationen auf diese Werte fest. Ein Typ kann durch einen Typnamen angesprochen werden, wenn er einen hat, oder er wird festgelegt durch ein Typliteral, das einen neuen Typ aus bereits bestehenden zusammensetzt.

Type      = TypeName | TypeLit | "(" Type ")" .
TypeName  = identifier | QualifiedIdent .
TypeLit   = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
            SliceType | MapType | ChannelType .

Die Sprache selbst hat bestimmte Typnamen vor deklariert. Andere werden mit Typdeklarationen eingeführt. Zusammengesetzte Typen — Array-, Struktur-, Zeiger-, Funktions-, Interface-, Slice-, Map- und Kanaltypen — können mithilfe von Typliteralen gebildet werden.

Jeder Typ T hat einen Ursprungstyp: Wenn T einer der vordeklarierten Boole'schen, numerischen oder String-Typen oder ein Typliteral ist, so ist der zugehörige Ursprungstyp T selbst. Sonst ist der Ursprungstyp von T der Typ, auf den T in seiner Typdeklaration verweist.

type (
	A1 = string
	A2 = A1
)

type (
	B1 string
	B2 B1
	B3 []B1
	B4 B3
)

Der Ursprungstyp von string, A1, A2, B1 und B2 ist string. Der Ursprungstyp von []B1, B3 und B4 ist []B1.

Methodenmenge

Einem Typ kann eine Methodenmenge zugeordnet werden. Die Methodenmenge eines Interface-Typs ist seine Schnittstelle. Die Methodenmenge jedes anderen namenbehafteten Typs T besteht aus allen mit Empfängertyp T deklarierten Methoden . Die Methodenmenge des korrespondierenden Zeigertyps *T besteht aus allen mit Empfängertyp *T oder T deklarierten Methoden. (Das heißt, sie enthält auch die Methodenmenge von T.) Es gibt weitere Regeln, die Strukturen mit eingebetteten Feldern betreffen, und die im Abschnitt Struktur-Typen beschrieben sind. Alle anderen Typen besitzen eine leere Methodenmenge. In einer Methodenmenge muss jede Methode einen eindeutigen Nicht-Leeren Bezeichner Methodennamen haben.

Die Methodenmenge eines Typs entscheidet über die Interfaces, die ein Typ implementiert; die Methoden können mit einem Empfänger dieses Typs gerufen werden.

Boole'sche Typen

Ein Boole'scher Typ vertritt die Menge der Boole'schen Wahrheitswerte, die durch die vordeklarierten Konstanten true und false benannt werden. Der vordeklarierte Boole'sche Typ ist bool, ein definierter Typ.

Numerische Typen

Ein numerischer Typ vertritt Mengen von Ganzzahl- und Gleitkommawerten. Die vordeklarierten architekturunabhängigen numerischen Typen sind:

uint8       die Menge aller  8-Bit Ganzzahlen ohne Vorzeichen (0 bis 255)
uint16      die Menge aller 16-Bit Ganzzahlen ohne Vorzeichen (0 bis 65535)
uint32      die Menge aller 32-Bit Ganzzahlen ohne Vorzeichen (0 bis 4294967295)
uint64      die Menge aller 64-Bit Ganzzahlen ohne Vorzeichen (0 bis 18446744073709551615)

int8        die Menge aller  8-Bit Ganzzahlen mit  Vorzeichen (-128 bis 127)
int16       die Menge aller 16-Bit Ganzzahlen mit  Vorzeichen (-32768 bis 32767)
int32       die Menge aller 32-Bit Ganzzahlen mit  Vorzeichen (-2147483648 bis 2147483647)
int64       die Menge aller 64-Bit Ganzzahlen mit  Vorzeichen (-9223372036854775808 bis 9223372036854775807)

float32     die Menge aller IEEE-754 32-Bit Gleitkommazahlen
float64     die Menge aller IEEE-754 64-Bit Gleitkommazahlen

complex64   die Menge aller Komplexzahlen mit float32 Real- und Imaginäranteil
complex128  die Menge aller Komplexzahlen mit float64 Real- und Imaginäranteil

byte        Alias für uint8
rune        Alias für int32

Der Wert einer n-Bit-Ganzzahl ist n Bits groß und wird mithilfe der Zweierkomplement-Arithmetik dargestellt.

Es gibt auch mehrere vordeklarierter numerischer Typen, deren Größe von der Go-Implementierung abhängt:

uint     entweder 32 oder 64 Bit
int      genauso groß wie uint
uintptr  Ganzzahl ohne Vorzeichen, groß genug, 
         um die uninterpretierten Bits eines Zeigerwerts aufzunehmen

Um portabel zu bleiben, sind alle numerischen Typen definierte Typen und unterscheiden sich deshalb voneinander, mit den Ausnahmen byte, welches ein Alias für uint8 ist, und rune, welches ein Alias ist für int32. Wenn verschiedene Typen gleichzeitig in einem Ausdruck oder einer Zuweisung vorkommen, dann ist Konversion nötig. Zum Beispiel sind int32 und int nicht derselbe Typ, selbst wenn sie auf einer bestimmten Architektur dieselbe Länge haben.

String-Typen

Ein String-Typ vertritt die Menge der String-Werte. Ein String-Wert ist eine (möglicherweise leere) Folge von Bytes. Strings sind unveränderlich: einmal erzeugt, ist es nicht mehr möglich, den Inhalt eines Strings zu verändern. Der vordeklarierte String-Typ ist string, ein definierter Typ.

Die Länge eines Strings s (seine Größe in Byte) kann mit der Standardfunktion len ermittelt werden. Ist der String eine Konstante, so ist die Länge eine Konstante zum Umwandlungszeitpunkt. Auf die Bytes eines Strings kann mit den Indices 0 bis len(s)-1 zugegriffen werden. Es ist nicht erlaubt, die Adresse eines solchen Elements abzugreifen: ist s[i] das i-te Byte des Strings, so ist &s[i] ungültig.

Array-Typen

Ein Array ist eine nummerierte Folge von Elementen eines einzigen Typs, der Elementtyp genannt wird. Die Anzahl der Elemente nennt man Länge, und sie ist niemals negativ.

ArrayType   = "[" ArrayLength "]" ElementType .
ArrayLength = Expression .
ElementType = Type .

Die Länge ist Bestandteil des Array-Typs; sie muss eine nicht negative Konstante sein, die als Wert vom Typ int darstellbar ist. Die Länge eines Arrays a kann mit der Standardfunktion len ermittelt werden. Die Elemente können durch die Ganzzahl-Indices 0 bis len(a)-1 angesprochen werden. Arrays sind immer eindimensional, können aber zu mehrdimensionale Typen geschachtelt werden.

[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64  // dasgleiche wie [2]([2]([2]float64))

Slice-Typen

Ein Slice [deutsch: Abschnitt,Scheibe,Schnitte, A.d.Ü.] beschreibt ein zusammenhängendes Teilstück eines zugrundeliegenden Arrays und ermöglicht den Zugang zu einer nummerierte Folge von Elementen dieses Arrays. Ein Slice-Typ bezeichnet die Menge aller Array-Abschnitte seines Elementtyps. Der Wert eines nicht-initialisierten Slice ist nil.

SliceType = "[" "]" ElementType .

Genau wie Arrays sind Slices indizierbar und besitzen eine Länge. Die Länge eines Slice s kann mit der Standardfunktion len ermittelt werden; anders als bei Arrays kann sie sich während der Laufzeit ändern. Die Elemente können durch die Ganzzahl-Indices 0 bis len(s)-1 angesprochen werden. Der Index eines Elements eines Slice kann kleiner sein als der Index desselben Elements im zugrundeliegenden Array.

Ein einmal initialisiertes Slice ist immer an ein zugrundeliegendes Array gebunden, das die Elemente enthält. Auf diese Weise teilt sich ein Slice Speicherplatz mit seinem Array und anderen Slices zum selben Array; im Gegensatz dazu stehen voneinander verschiedene Arrays für voneinander verschiedene Speicherplätze.

Das einem Slice zugrundeliegende Array kann sich über das Ende des Slice hinaus ausdehnen. Das Maß für diese Ausdehnung ist die Kapazität: sie ist die Summe aus der Länge des Slice und der Restlänge des Arrays, die an das Slice anschließt. Ein Slice der Länge bis hin zu dieser Kapazität kann erzeugt werden, indem man vom Original-Slice neu aufschneidet. Die Kapazität eines Slice s kann mit der Standardfunktion cap(s) ermittelt werden.

Man erzeugt einen neue initialisierten Slice-Wert für einen Elementtyp T mit der Standardfunktion make mit einem Slice-Typ und einem Parameter für die Länge und optional einem für die Kapazität. Ein mit make erzeugtes Slice reserviert ein neues verstecktes Array, auf das sich der zurückgegebene Slice-Wert bezieht. Das heißt, die Ausführung von:

make([]T, length, capacity)

erzeugt das gleiche Slice, wie die Anforderung eines Arrays mit anschließendem Aufschneiden; d.h. diese beiden Ausdrücke sind gleichbedeutend:

make([]int, 50, 100)
new([100]int)[0:50]

Genau wie Arrays sind Slices immer eindimensional, können aber zu mehrdimensionale Objekten geschachtelt werden. Bei Arrays aus Arrays haben die inneren Arrays, durch die Konstruktion, immer die gleichen Längen, hingegen können sich bei Slices aus Slices (oder auch Arrays aus Slices) die inneren Längen dynamisch ändern. Darüberhinaus müssen dort die inneren Slices einzeln initialisiert werden.

Struktur-Typen

Ein Struktur ist eine Folge von namensbehafteten Elementen, die Felder genannt werden, die wiederum jeweils einen Namen und einen Typ besitzen. Feldnamen können explizit (Bezeichnerliste) oder implizit (eingebettetes Feld) festgelegt werden. Innerhalb einer Struktur müssen nicht-leere Feldnamen eindeutig sein.

StructType     = "struct" "{" { FieldDecl ";" } "}" .
FieldDecl      = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ "*" ] TypeName .
Tag            = string_lit .
// Eine leere Struktur.
struct {}

// Eine Struktur mit 6 Feldern.
struct {
    x, y int
    u float32
    _ float32  // Reserve
    A *[]int
    F func()
}

Ein Feld, das mit einem Typ, aber nicht mit einem expliziten Feldnamen deklariert wird, heißt eingebettetes Feld, Ein eingebettetes Feld muss als ein Typname T oder als ein Zeiger auf einen Nicht-Interface-Typ *T festgelegt sein, und T selbst darf kein Zeigertyp sein. Der unqualifizierte Typname gilt dann als Feldname.

// Eine Struktur mit vier eingebetteten Feldern vom Typ T1, *T2, P.T3 und *P.T4
struct {
    T1        // Feldname ist T1
    *T2       // Feldname ist T2
    P.T3      // Feldname ist T3
    *P.T4     // Feldname ist T4
    x, y int  // Feldnamen sind x und y
}

Die folgende Deklaration ist ungültig, weil Feldnamen in einem Struktur-Typ eindeutig sein müssen:

struct {
    T     // kollidiert mit den eingebetteten Feldern *T und *P.T
    *T    // kollidiert mit den eingebetteten Feldern T und *P.T
    *P.T  // kollidiert mit den eingebetteten Feldern T und *T
}

Ein Feld oder eine Methode f eines eingebetteten Feldss einer Struktur x nennt man befördert [englisch: promoted, A.d.Ü.], wenn x.f ein gültiger Selektor ist, der das Feld oder die Methode f benennt.

Beförderte Felder verhalten sich wie die gewöhnlichen Felder einer Struktur, nur, dass sie nicht als Feldnamen in einem Verbundliterals der Struktur benutzt werden können.

Zu einem gegebenen Struktur-Typ S und einem definierten Typ T werden beförderte Methoden in der Methodenmenge der Struktur wie folgt aufgenommen:

Auf eine Felddeklaration darf optional eine Markierung [englisch: tag, A.d.Ü.] folgen, das für alle Felder der zugehörigen Felddeklaration zum Attribut wird. Ein leerer Markierungs-String ist äquivalent zu einer fehlenden Markierung. Markierungen können mit einem Reflexions-Interface sichtbar gemacht werden und gehören zur Typidentität von Strukturen; ansonsten werden sie ignoriert.

struct {
    x, y float64 "" // ein leerer Markierungs-String ist wie kein String;
                    // "ein jeglicher String darf Markierung sein"
    _    [4]byte "ceci n'est pas un champ de structure"
}

// Eine Struktur, die dem "TimeStamp protocol buffer" entspricht.
// Die Markierungs-Strings definieren die "protocol buffer"-Feldnummern;
// Sie richten sich nach der Konvention, die im reflect-Paket beschrieben ist.
struct {
    microsec  uint64 `protobuf:"1"`
    serverIP6 uint64 `protobuf:"2"`
}

Zeigertypen

Ein Zeigertyp bezeichnet die Menge aller Zeiger zu Variablen eines vorgegebenen Typs, der Basistyp des Zeigers genannt wird. Der Wert eines nicht-initialisierten Zeigers ist nil.

PointerType = "*" BaseType .
BaseType    = Type .
*Point
*[4]int

Funktionstypen

Ein Funktionstyp bezeichnet die Menge aller Funktionen mit den gleichen Parameter- und Ergebnistypen. Der Wert einer nicht-initialisierten Variablen eines Funktionstyps ist nil.

FunctionType   = "func" Signature .
Signature      = Parameters [ Result ] .
Result         = Parameters | Type .
Parameters     = "(" [ ParameterList [ "," ] ] ")" .
ParameterList  = ParameterDecl { "," ParameterDecl } .
ParameterDecl  = [ IdentifierList ] [ "..." ] Type .

Innerhalb einer Parameter- oder Ergebnisliste müssen die Bezeichner (IdentifierList) entweder alle vorhanden oder alle abwesend sein. Wenn vorhanden, vertritt jeder Name ein Element (Parameter oder Ergebnis) des bezeichneten Typs und alle nicht-leeren Namen in der Signatur müssen eindeutig sein. Wenn nicht vorhanden, vertritt jeder Typ ein Element dieses Typs. Parameter- und Ergebnislisten stehen immer in Klammern — mit einer Ausnahme: wenn es genau ein Ergebnis ohne Namen gibt, dann darf der Ergebnistyp ohne Klammern geschrieben werden.

Der letzte Input-Parameter in der Signatur einer Funktion kann ein Typ mit dem Präfix ... sein. Eine Funktion mit einem solchen Parameter nennt man variadisch, und sie kann mit 0 oder mehr Argumenten für diesen Parameter aufgerufen werden.

func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)

Interface-Typen

Ein Interface-Typ legt eine Methodenmenge fest, die man seine Schnittstelle nennt. Eine Variable vom Typ Interface kann den Wert all der Typen speichern, deren Methodenmenge eine Obermenge dieser Schnittstelle ist. Man sagt, solch ein Typ implementiert das Interface. Der Wert einer nicht-initialisierten Variablen vom Typ Interface ist nil.

InterfaceType      = "interface" "{" { MethodSpec ";" } "}" .
MethodSpec         = MethodName Signature | InterfaceTypeName .
MethodName         = identifier .
InterfaceTypeName  = TypeName .

Wie bei anderen Methodensätzen, muss auch bei einem Interface-Typ jede Methode einen eindeutigen Nicht-Leeren Namen haben.

// Ein einfaches Datei-Interface
interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
    Close()
}

Ein Interface kann durch mehr als einen Typ implementiert werden. Wenn zum Beispiel die beiden Typen S1 und S2 die folgenden Methoden besitzen:

func (p T) Read(b Buffer) bool { return … }
func (p T) Write(b Buffer) bool { return … }
func (p T) Close() { … }

(wobei T sowohl für S1 als auch S2 steht), dann wird das Datei-Interface sowohl durch S1 als auch durch S2 implementiert, egal was S1 oder S2 sonst noch an Methoden besitzen oder teilen.

Ein Typ implementiert jedes Interface, das aus einer beliebigen Untermenge seiner Methoden besteht und kann deshalb mehrere verschiedene Interfaces implementieren. Zum Beispiel implementieren alle Typen das Leere Interface:

interface{}

Oder nehmen wir diese Interface-Beschreibung, die in einer Typdeklaration steht, um ein Interface namens Locker zu definieren:

type Locker interface {
    Lock()
    Unlock()
}

Wenn also S1 und S2 außerdem noch folgendes implementieren:

func (p T) Lock() { … }
func (p T) Unlock() { … }

, dann implementieren sie das Locker-Interface genauso wie das Datei-Interface.

Ein Interface T kann anstelle von Methodenbeschreibungen einen (möglicherweise qualifizierten) Interface-Typnamen E benutzen. Dies nennt man Einbetten des Interfaces E in T; es ergänzt alle (exportierten und nicht-exportierten) Methoden von E zum Interface T.

type ReadWrite interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
}

type File interface {
    ReadWrite  // dasgleiche wie die Beschreibung aller Methoden von ReadWrite
    Locker     // dasgleiche wie die Beschreibung aller Methoden von Locker
    Close()
}

type LockedFile interface {
    Locker
    File        // verboten: Lock und Unlock sind nicht eindeutig
    Lock()      // verboten: Lock ist nicht eindeutig
}

Ein Interface-Typ T darf sich nicht selbst einbetten, auch nicht indirekt über einen anderen Interface-Typ, der T einbettet.

// verboten: Bad darf sich selbst nicht einbetten
type Bad interface {
    Bad
}

// verboten: Bad1 darf sich selbst nicht über Bad2 einbetten
type Bad1 interface {
    Bad2
}
type Bad2 interface {
    Bad1
}

Map-Typen

Eine Map ist eine ungeordnete Menge von Elementen eines Typs, genannt Elementtyp, die durch eine Menge von eindeutigen Schlüsseln eines anderen Typs, genannt Schlüsseltyp, indiziert werden. Der Wert einer nicht-initialisierten Map ist nil.

MapType     = "map" "[" KeyType "]" ElementType .
KeyType     = Type .

Für Schlüsseltypen müssen die Vergleichsoperatoren == und != komplett definiert sein; darum sind Funktionen, Maps oder Slices keine Schlüsseltypen. Ist der Schlüsseltyp ein Interface, so müssen diese Vergleichsoperatoren für die dynamischen Schlüsselwerte definiert sein; Nichterfüllung führt zu einem Laufzeitfehler.

map[string]int
map[*T]struct{ x, y float64 }
map[string]interface{}

Die Anzahl der Map-Elemente nennt man Länge. Für eine Map m kann man sie mit der Standardfunktion len ermitteln, sie kann sich zur Laufzeit ändern. Man kann Elemente mittels Zuweisungen hinzufügen, man kann sie mit Indexausdrücken abrufen, und man kann sie mit der Standardfunktion delete entfernen.

Eine neue, leere Map erzeugt man mit der Standardfunktion make, der man den Map-Typ und wahlweise auch einen Kapazitätshinweis als Argumente mitgibt:

make(map[string]int)
make(map[string]int, 100)

Die Anfangskapazität beschränkt die Größe nicht: Maps wachsen mit der Anzahl der in ihnen gespeicherten Elemente, mit der Ausnahme von nil-Maps. Eine nil-Map entspricht einer leeren Map, nur dass keine Elemente hinzugefügt werden können.

Kanaltypen

Ein Kanal bietet einen Mechanismus, mit dem nebenläufige Funktionen kommunizieren können indem sie den Wert eines festgelegten Elementtyps senden und empfangen. Der Wert eines nicht-initialisierten Kanals ist nil.

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

Der optionale Operator <- legt die Kanal-Richtung fest, entweder fürs Senden oder fürs Empfangen. Ist keine Richtung vorgegeben, so ist der Kanal bidirektional. Ein Kanal kann auf nur Senden oder nur Empfangen durch Konversion oder Zuweisung eingeschränkt werden.

chan T          // kann zum Senden und Empfangen von Werten vom Typ T benutzt werden
chan<- float64  // kann nur zum Senden von float64 benutzt werden
<-chan int      // kann nur zum Empfangen von int benutzt werden

Der Operator <- gehört zum jeweils linkest möglichen chan:

chan<- chan int    // gleichwertig zu chan<- (chan int)
chan<- <-chan int  // gleichwertig zu chan<- (<-chan int)
<-chan <-chan int  // gleichwertig zu <-chan (<-chan int)
chan (<-chan int)

Einen neuen, initialisierten Kanal erzeugt man mit der Standardfunktion make, der man den Kanaltyp und wahlweise auch die Kapazität als Argumente mitgibt:

make(chan int, 100)

Die Kapazität, angegeben in Anzahl von Elementen, legt die Größe des Kanalpuffers fest. Ist die Kapazität nicht angegeben oder null, so handelt es sich um einen ungepufferten Kanal und Kommunikationsoperationen gelingen nur, wenn sowohl Sender als auch Empfänger bereit sind. Sonst handelt es sich um einen gepufferten Kanal und Kommunikationsoperationen gelingen ohne Blockieren, solange der Puffer nicht voll ist (beim Senden) oder nicht leer (beim Empfangen). Ein nil-Kanal ist niemals bereit.

Ein Kanal kann mit der Standardfunktion close geschlossen werden. In Form der Mehrfachzuweisung meldet der Empfangsoperator auch, ob ein empfangener Wert gesendet wurde, bevor der Kanal geschlossen wurde.

Ein einzelner Kanal kann aus beliebig vielen Goroutinen heraus benutzt werden in Sendeanweisungen und Empfangsanweisungen sowie in Aufrufen der Standardfunktionen cap und len, und zwar ohne zusätzliche Synchronisation. Kanäle arbeiten wie FIFO-Warteschlangen (first in first out). Sendet zum Beispiel eine Goroutine Werte über einen Kanal und eine zweite empfängt sie, so werden sie in derselben Reihenfolge empfangen wie gesendet.

Eigenschaften von Typen und Werten

Typidentität

Zwei Typen sind entweder identisch oder verschieden.

Ein definierter Typ ist von jedem anderen Typ verschieden. Zwei andere Typen sind dann identisch, wenn ihre zugrundeliegenden Typliterale in Ihrer Struktur gleichwertig sind, das heißt, wenn sie dieselbe Literalstruktur haben und die entsprechenden Komponenten vom selben Typ sind. Im Einzelnen:

Geht man von diesen Deklarationen aus:

type (
	A0 = []string
	A1 = A0
	A2 = struct{ a, b int }
	A3 = int
	A4 = func(A3, float64) *A0
	A5 = func(x int, _ float64) *[]string
)

type (
	B0 A0
	B1 []string
	B2 struct{ a, b int }
	B3 struct{ a, c int }
	B4 func(int, float64) *B0
	B5 func(x int, y float64) *A1
)

type	C0 = B0

dann sind folgende Typen identisch:

A0, A1 und []string
A2 und struct{ a, b int }
A3 und int
A4, func(int, float64) *[]string, und A5

B0 und C0
[]int und []int
struct{ a, b *T5 } und struct{ a, b *T5 }
func(x int, y float64) *[]string, func(int, float64) (result *[]string), und A5

B0 und B1 sind verschieden, weil sie neue Typen mit unterschiedlichen Typdefinitionens sind; func(int, float64) *B0 und func(x int, y float64) *[]string sind verschieden, weil B0 von []string verschieden ist.

Zuweisbarkeit

Ein Wert x ist einer Variablen T zuweisbar ("x ist zuweisbar zu T"), wenn:

Darstellbarkeit

Eine Konstante x ist durch einen Wert vom Typ T darstellbar, wenn eine der folgenden Bedingungen erfüllt ist:

x                   T           x ist durch einen Wert vom Typ T darstellbar, weil:

'a'                 byte        97 zur Menge der Byte-Werte gehört
97                  rune        rune ein Alias für int32 ist, und 97 zur Menge der 32-Bit-Ganzzahlen gehört
"foo"               string      "foo" zur Menge der String-Werte gehört
1024                int16       1024 zur Menge der 16-Bit-Ganzzahlen gehört
42.0                byte        42 zur Menge der 8-Bit-Ganzzahlen gehört
1e10                uint64      10000000000 zur Menge der 64-Bit-Ganzzahlen ohne Vorzeichen gehört
2.718281828459045   float32     2.718281828459045 zu 2.7182817 gerundet wird, was zur Menge der float32-Werte gehört
-1e-1000            float64     -1e-1000 zu IEEE -0.0 gerundet und dann zu 0.0 vereinfacht wird
0i                  int         0 ein Ganzzahlwert ist
(42 + 0i)           float32     42.0 (mit dem Imaginärteil 0) zur Menge der float32-Werte gehört
x                   T           x ist nicht durch einen Wert vom Typ T darstellbar, weil:

0                   bool        0 nicht zur Menge der Boole'schen Werte gehört
'a'                 string      'a' eine Rune ist; es gehört nicht zur Menge der String-Werte
1024                byte        1024 nicht zur Menge der 8-Bit-Ganzzahlen ohne Vorzeichen gehört
-1                  uint16      -1 nicht zur Menge der 16-Bit-Ganzzahlen ohne Vorzeichen gehört
1.1                 int         1.1 kein ganzzahliger Wert ist
42i                 float32     (0 + 42i)nicht zur Menge der float32-Werte gehört
1e1000              float64     1e1000 nach dem Runden zu IEEE +Inf überläuft

Blöcke

Ein Block ist eine eventuell leere Folge von Deklarationen und Anweisungen, eingeschlossen in geschweiften Klammern.

Block = "{" StatementList "}" .
StatementList = { Statement ";" } .

Außer den expliziten Blöcken im Quellkode gibt es noch die impliziten:

  1. Der All-Block enthält den gesamten Go-Quelltext.
  2. Jedes Paket besitzt einen Paketblock, der den gesamten Go-Quelltext dieses Pakets enthält.
  3. Jede Datei besitzt einen Dateiblock, der den gesamten Go-Quelltext dieser Datei enthält.
  4. Für jede If-, For- und Switch-Anweisung wird angenommen, dass sie sich in ihrem eigenen impliziten Block befindet.
  5. Jede Klausel in einer Switch- oder Select-Anweisung stellt einen impliziten Block dar.

Blöcke können geschachtelt werden. Sie beeinflussen Reichweiten.

Deklarationen und Reichweiten

Eine Deklaration bindet jeden Bezeichner, ausgenommen den Leeren Bezeichner, an eine Konstante, einen Typ, eine Variable, eine Funktion, eine Sprungmarke, oder an ein Paket. Jeder Bezeichner in einem Programm muss deklariert werden. Kein Bezeichner kann zweimal im gleichen Block deklariert werden; das gilt auch für Datei- und Paketblock.

Der Leere Bezeichner kann wie jeder andere Bezeichner in einer Deklaration benutzt werden, aber das führt zu keiner neuen Bindung, er ist also nicht deklariert. Im Paketblock darf der Bezeichner init nur in init-Funktions-Deklarationen verwendet werden; auch dieser, wie der Leere Bezeichner, führt zu keiner neuen Bindung.

Declaration   = ConstDecl | TypeDecl | VarDecl .
TopLevelDecl  = Declaration | FunctionDecl | MethodDecl .

Die Reichweite [englisch: scope, A.d.Ü.] eines deklarierten Bezeichners ist der Bereich des Quellkodes, in dem der Bezeichner die in der Deklaration bestimmte Konstante, den Typ, die Variable, die Funktion, die Sprungmarke oder das Paket bezeichnet.

In Go werden Reichweiten lexikalisch durch Blöcke festgelegt.

  1. Die Reichweite eines vordeklarierten Bezeichners ist der All-Block.
  2. Die Reichweite eines Bezeichners einer Konstanten, eines Typs, einer Variablen oder einer Funktion (aber nicht einer Methode), welcher auf oberster Ebene (also nicht innerhalb einer Funktion) deklariert ist, ist der Paketblock.
  3. Die Reichweite des Paketnamens eines importierten Pakets ist der Dateiblock der Datei, welche die Importdeklaration enthält.
  4. Die Reichweite des Bezeichners eines Methodenempfängers, eines Funktionsparameters oder einer Ergebnisvariablen ist der Funktionsrumpf.
  5. Die Reichweite eines Konstanten- oder Variablen-Bezeichners, welcher innerhalb einer Funktion deklariert ist, beginnt am Ende der Konstantenfestlegung (ConstSpec), der Variablenfestlegung (VarSpec) oder der Variablen-Kurzdeklaration (ShortVarDecl), und endet am Ende des innersten Blocks, der diese Deklaration enthält.
  6. Die Reichweite eines Typ-Bezeichners, welcher innerhalb einer Funktion deklariert ist, beginnt beim Bezeichner in der Typfestlegung (TypSpec) und endet am Ende des innersten Blocks, der diese Deklaration enthält.

Ein Bezeichner, welcher in einem Block deklariert ist, kann in einem Block innerhalb dieses Blocks erneut deklariert werden. Solange der Bezeichner in der Reichweite der inneren Deklaration auftaucht, bezeichnet er die Einheit, die dort deklariert ist.

Die Paket-Klausel ist keine Deklaration; der Paketname hat keine Reichweite. Seine Aufgabe ist es, die Dateien zu kennzeichnen, die zum selben Paket gehören, und den Standard-Paketnamen fürs Importieren festzulegen.

Reichweite von Sprungmarken

Sprungmarken werden durch markierte Anweisungen deklariert, und werden benutzt in den Break-, Continue- und Goto-Anweisungen. Es ist nicht erlaubt, eine Sprungmarke zu deklarieren, die nie benutzt wird. Anders als andere Bezeichner sind Sprungmarken nicht an Blöcke gebunden und kollidieren auch nicht mit anderen Bezeichnern, die keine Sprungmarken sind. Die Reichweite einer Sprungmarke ist der Rumpf der Funktion, in der sie deklariert ist, ohne die Funktionsrümpfe der darin enthaltenen Funktionen.

Der Leere Bezeichner

Der Leere Bezeichner wird dargestellt durch den Unterstrich _. Er dient als namenloser Platzhalter anstelle eines regulären Bezeichners und hat spezielle Bedeutung in Deklarationen und Zuweisungen.

Vordeklarierte Bezeichner

Die folgenden Bezeichner sind implizit im All-Block deklariert:

Typen:
	bool byte complex64 complex128 error float32 float64
	int int8 int16 int32 int64 rune string
	uint uint8 uint16 uint32 uint64 uintptr

Konstanten:
	true false iota

Nullwert:
	nil

Funktionen:
	append cap close complex copy delete imag len
	make new panic print println real recover

Exportierte Bezeichner

Ein Bezeichner kann exportiert werden, um anderen Paketen darauf Zugriff zu gewähren. Ein Bezeichner wird exportiert, wenn:

  1. das erste Zeichen des Bezeichnernamens ein Unicode-Großbuchstabe (Unicode-Klasse "Lu") ist, und
  2. der Bezeichner im Paketblock deklariert ist, oder ein Feldname oder ein Methodenname ist.

Alle anderen Bezeichner werden nicht exportiert.

Eindeutigkeit von Bezeichnern

In einer gegebenen Menge von Bezeichnern wird ein Bezeichner eindeutig genannt, wenn er sich von allen anderen in der Menge unterscheidet. Zwei Bezeichner unterscheiden sich voneinander, wenn sie verschieden geschrieben werden oder wenn sie in verschiedenen Paketen erscheinen aber nicht exportiert wurden. Sonst sind sie gleich.

Konstantendeklarationen

Eine Konstantendeklaration bindet eine Liste von Bezeichnern (die Konstantennamen) an die Werte einer Liste von Konstantenausdrücken. Die Anzahl der Bezeichner muss mit der Anzahl der Ausdrücke übereinstimmen. Der n-te Bezeichner auf der linken Seite wird an den n-ten Ausdruck auf der rechten Seite gebunden.

ConstDecl      = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
ConstSpec      = IdentifierList [ [ Type ] "=" ExpressionList ] .

IdentifierList = identifier { "," identifier } .
ExpressionList = Expression { "," Expression } .

Ist ein Typ angegeben, so sind alle Konstanten von diesem Typ, und die Ausdrücke müssen diesem Typ zuweisbar sein. Ist kein Typ angegeben, so sind die Konstanten vom jeweiligen Typ der zugehörigen Ausdrücke. Ist der Wert eines Ausdrucks eine typfreie Konstante, so bleibt auch die deklarierte Konstante typfrei und der Konstantenbezeichner benennt den Konstantenwert. Wenn beispielsweise der Ausdruck ein Gleitkommaliteral ist, dann benennt der Konstantenbezeichner eine Gleitkommakonstante, selbst wenn der gebrochene Anteil des Literals null ist.

const Pi float64 = 3.14159265358979323846
const zero = 0.0             // typfreie Gleitkommakonstante
const (
    size int64 = 1024
    eof        = -1          // typfreie Ganzzahlkonstante
)
const a, b, c = 3, 4, "foo"  // a = 3, b = 4, c = "foo", typfreie Ganzzahl- und Stringkonstanten
const u, v float32 = 0, 3    // u = 0.0, v = 3.0

Innerhalb einer geklammerten const-Deklarationsliste kann die Ausdrückeliste weggelassen werden, außer beim ersten ConstSpec. Eine solche leere Liste entspricht der letzten vorhergehenden nicht-leeren Ausdrückeliste und dessen Typen, soweit vorhanden. Das Weglassen ist also gleichbedeutend mit dem Wiederholen der vorhergehenden Liste. Die Anzahl der Bezeichner muss gleich der Anzahl der Ausdrücke der vorhergehenden Liste sein. Zusammen mit dem iota-Konstantengenerator ergibt das eine einfache Deklaration von aufeinanderfolgenden Werten:

const (
    Sunday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Partyday
    numberOfDays  // diese Konstante wird nicht exportiert
)

Iota

Innerhalb einer Konstantendeklaration steht der vordeklarierte Bezeichner iota für aufeinanderfolgende typfreie Ganzzahlkonstanten. Ihr Wert ist die Nummer des zugehörigen ConstSpec dieser Konstantendeklaration und beginnt bei 0. Damit kann man eine Menge zusammengehöriger Konstanten konstruieren:

const (
    c0 = iota  // c0 == 0
    c1 = iota  // c1 == 1
    c2 = iota  // c2 == 2
)

const (
    a = 1 << iota  // a == 1
    b = 1 << iota  // b == 2
    c = 3          // c == 3
    d = 1 << iota  // d == 8
)

const (
    u         = iota * 42  // u == 0     (typfreie Ganzzahlkonstante)
    v float64 = iota * 42  // v == 42.0  (Gleitkommakonstante)
    w         = iota * 42  // w == 84    (typfreie Ganzzahlkonstante)
)

const x = iota  // x == 0
const y = iota  // y == 0

Definitionsgemäß haben alle in demselben ConstSpec benutzten iota denselben Wert:

const (
    bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0 iota == 0
    bit1, mask1                           // bit1 == 2, mask1 == 1 iota == 1
    _, _                                  //                       iota == 2 (nicht benutzt)
    bit3, mask3                           // bit3 == 8, mask3 == 7 iota == 3
)

Dieses letzte Beispiel macht Gebrauch von der impliziten Wiederholung der letzten nicht-leeren Ausdrückeliste.

Typdeklarationen

Eine Typdeklaration bindet einen Bezeichner, den Typnamen, an einen Typ. Es gibt zwei Arten von Typdeklarationen: Aliasdeklarationen und Typdefinitionen.

TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .

Aliasdeklarationen

Eine Aliasdeklaration bindet einen Bezeichner an einen vorgegebenen Typ.

AliasDecl = identifier "=" Type .

Innerhalb der Reichweite des Bezeichners dient sie als Alias für den Typ.

type (
    nodeList = []*Node // nodeList und []*Node sind identische Typen
    Polar    = polar   // Polar und polar bezeichnen identische Typen
)

Typdefinitionen

Eine Typdefinition erzeugt einen neuen einzigartigen Typ mit demselben darunterligenden Typ und denselben Operationen wie der vorgegebene Typ, und bindet daran einen Bezeichner.

TypeDef = identifier Type .

Den neuen Typ nennt man einen definierten Typ. Er unterscheidet sich von allen anderen Typen, wozu auch der Typ gehört, aus dem er erzeugt wurde.

type (
    Point struct{ x, y float64 } // Point and struct{ x, y float64 } sind verschiedene Typen
    polar Point                  // polar and Point bezeichnen verschiedene Typen
)

type TreeNode struct {
    left, right *TreeNode
    value *Comparable
}

type Block interface {
    BlockSize() int
    Encrypt(src, dst []byte)
    Decrypt(src, dst []byte)
}

Der definierte Typ kann Methoden haben, die mit ihm verbunden sind. Er erbt keine Methoden, die an den vorgegebenen Typ gebunden sind; die Methodenmenge eines Interface-Typs und der Elemente eines zusammengesetzten Typs bleibt jedoch erhalten:

// Ein Mutex ist ein Datentyp mit zwei Methoden, Lock und Unlock.
type Mutex struct         { /* Mutex-Felder */ }
func (m *Mutex) Lock()    { /* Lock-Implementierung */ }
func (m *Mutex) Unlock()  { /* Unlock-Implementierung */ }

// NewMutex hat denselben Aufbau wie Mutex aber seine Methodenmenge ist leer.
type NewMutex Mutex

// Die Methodenmenge des dem PtrMutex zugrundeliegenden Typs *Mutex bleibt erhalten,
// aber die Methodenmenge von PtrMutex ist leer.
type PtrMutex *Mutex

// Die Methodenmenge von *PrintableMutex enthält die Methoden
// Lock und Unlock, die an das eingebettete Feld Mutex gebunden sind.
type PrintableMutex struct {
    Mutex
}

// MyBlock ist ein Interface-Typ mit derselben Methodenmenge wie Block.
type MyBlock Block

Mit Typdedefinitionen kann man neue Boole'sche, numerische oder String-Typen definieren und ihnen Methoden zuweisen:

type TimeZone int

const (
    EST TimeZone = -(5 + iota)
    CST
    MST
    PST
)

func (tz TimeZone) String() string {
    return fmt.Sprintf("GMT+%dh", tz)
}

Variablendeklarationen

Eine Variablendeklaration erzeugt eine oder mehrere Variablen, bindet daran die zugehörigen Bezeichner und gibt jeder einen Typ und einen Initialwert.

VarDecl     = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec     = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
var i int
var U, V, W float64
var k = 0
var x, y float32 = -1, -2
var (
    i int
    u, v, s = 2.0, 3.0, "bar"
)
var re, im = complexSqrt(-1)
var _, found = entries[name]  // "Nachschlagen" in der Map; nur "found" interessiert

Wenn eine Liste von Ausdrücken angegeben ist, dann werden die Variablen entsprechend den Zuweisungsregeln initialisiert, Sonst wird jede Variable mit ihrem Nullwert initialisiert.

Ist der Typ angegeben, so wird jeder Variablen dieser Typ zugeordnet. Sonst wird jeder Variablen der Typ des entsprechenden Initialwerts zugeordnet. Ist der Wert eine typfreie Konstante, so wird er zuerst zu seinem Standardtyp konvertiert; wenn sie also ein typfreier Boole'scher Wert ist, wird sie zunächst zum Typ bool konvertiert. Der vordeklarierte Wert nil kann nicht zum Initialisieren einer Variable ohne expliziten Typ benutzt werden.

var d = math.Sin(0.5)  // d ist float64
var i = 42             // i ist int
var t, ok = x.(T)      // t ist T, ok ist bool
var n = nil            // verboten

Go-Implementierungseinschränkung: Ein Compiler kann es verbieten, eine Variable innerhalb eines Funktionsrumpfs zu deklarieren, wenn die Variable nie benutzt wird.

Variablenkurzdeklarationen

Eine Variablenkurzdeklaration schreibt man so:

ShortVarDecl = IdentifierList ":=" ExpressionList .

Es ist die Kurzform einer Variablendeklaration mit Ausdrücken fürs Initialisieren aber ohne Angabe eines Typs.

"var" IdentifierList = ExpressionList .
i, j := 0, 10
f := func() int { return 7 }
ch := make(chan int)
r, w := os.Pipe(fd)  // os.Pipe() gibt zwei Werte zurück
_, y, _ := coord(p)  // coord() gibt drei Werte zurück; nur die y-Koordinate interessiert

Anders als die normale Variablendeklaration darf die Variablenkurzdeklaration Variablen redeklarieren, wenn sie vorher im selben Block (oder in den Parameterlisten, wenn der Funktionsrumpf der Block ist) mit demselben Typ deklariert worden sind und mindestens eine der nicht-leeren Variablen neu ist. Daraus folgt, dass eine erneute Deklaration nur in Mehrfach-Variablenkurzdeklaration vorkommen kann. Eine erneute Deklaration führt keine neue Variable ein; sie weist der ursprünglichen Variablen nur einen neuen Wert zu.

field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset)  // deklariert offset erneut
a, a := 1, 2                              // verboten: entweder Doppeldeklaration von a, oder keine neue Variable, wenn bereits vorher schon deklariert

Variablenkurzdeklarationen dürfen nur innerhalb von Funktionen erscheinen. In einigen Fällen, wie etwa als Startschritt in If-, For oder Switch-Anweisungen können sie benutzt werden, um lokale temporäre Variablen zu deklarieren.

Funktionsdeklarationen

Eine Funktionsdeklaration bindet einen Bezeichner, den Funktionsnamen, an eine Funktion.

FunctionDecl = "func" FunctionName Signature [ FunctionBody ] .
FunctionName = identifier .
FunctionBody = Block .

Wenn die Signatur einer Funktion Ergebnisparameter deklariert, dann muss die Anweisungsliste des Funktionsrumpfs mit einer Terminierenden Anweisung enden.

func IndexRune(s string, r rune) int {
	for i, c := range s {
		if c == r {
			return i
		}
	}
    // verboten: return fehlt
}

Eine Funktionsdeklaration darf den Funktionsrumpf weglassen. Solcherart liefert sie die Signatur für eine außerhalb von Go implementierte Funktion, etwa einer Assembler-Routine.

func min(x int, y int) int {
    if x < y {
        return x
    }
    return y
}

func flushICache(begin, end uintptr)  // extern implementiert

Methodendeklarationen

Eine Methode ist eine Funktion mit einem Empfänger. Eine Methodendeklaration bindet einen Bezeichner, den Methodennamen, an eine Methode und verknüpft die Methode mit dem Basistyp des Empfängers.

MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .
Receiver   = Parameters .

Der Empfänger wird durch einen eigenen Parameterabschnitt vor dem Methodennamen festgelegt. Dieser Abschnitt muss einen einzigen (nicht-variadischen) Parameter, nämlich den Empfänger, deklarieren. Sein Typ muss in der Form T oder *T (eventuell in Klammern) vorliegen und T muss ein Typname sein. Der durch T bezeichnete Typ wird Basistyp des Empfängers genannt; er darf kein Zeiger- oder Interface-Typ sein und er muss im selben Paket wie die Methode definiert sein. Man sagt, die Methode sei an den Basistyp gebunden, und der Methodenname ist nur innerhalb von Selektoren für die Typen T oder *T sichtbar.

Ein nicht-leerer Bezeichner für einen Empfänger in der Methodensignatur muss eindeutig sein. Wird auf den Empfängerwert im Methodenrumpf nicht zugegriffen, so kann sein Bezeichner in der Deklaration weggelassen werden. Dasselbe gilt auch ganz allgemein für Parameter von Funktionen und Methoden.

Für einen Basistyp müssen die Bezeichner, ohne den leeren, eindeutig sein. Ist der Basistyp ein Struktur-Typ, so müssen sich die Methoden- und die nicht-leeren Feldnamen unterscheiden.

Für einen gegebenen Typ Point bindet die Deklaration:

func (p *Point) Length() float64 {
    return math.Sqrt(p.x * p.x + p.y * p.y)
}

func (p *Point) Scale(factor float64) {
    p.x *= factor
    p.y *= factor
}

die Methoden Length und Scale mit dem Empfängertyp *Point an den Basistyp Point.

Der Typ einer Methode ist der Typ einer Funktion mit dem Empfänger als erstem Argument. Zum Beispiel hat die Methode Scale den Typ:

func(p *Point, factor float64)

Allerdings ist eine so deklarierte Funktion keine Methode.

Ausdrücke

Ausdruck bezeichnet die Berechnung eines Wertes durch die Anwendung von Operatoren und Funktionen auf Operanden.

Operanden

Operanden bezeichnen die einzelnen Werte in einem Ausdruck. Ein Operand kann ein Literal sein, ein (möglicherweise qualifizierter) Nicht-leerer Bezeichner für eine Konstante, Variable oder Funktion, oder ein Klammerausdruck.

Der Leere Bezeichner darf nur auf der linken Seite einer Zuweisung erscheinen.

Operand     = Literal | OperandName | "(" Expression ")" .
Literal     = BasicLit | CompositeLit | FunctionLit .
BasicLit    = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
OperandName = identifier | QualifiedIdent.

Qualifizierte Bezeichner

Ein qualifizierter Bezeichner ist ein Bezeichner, der mit einen Paketnamen als Präfix qualifiziert wird. Weder der Paketname noch der Bezeichner dürfen leer sein.

QualifiedIdent = [ PackageName "." ] identifier .

Ein qualifizierter Bezeichner greift auf einen Bezeichner in einem anderen Paket durch, welches importiert worden sein muss. Dieser Bezeichner wiederum muss exportiert und im Paket-Block dieses Pakets deklariert sein.

math.Sin	// bezeichnet die Funktion Sin im Paket math

Verbundliterale

Verbundliterale bilden Werte für Strukturen, Arrays, Slices und Maps und erzeugen einen neuen Wert jedesmal dann, wenn sie ausgewertet werden. Sie bestehen aus dem Typ des Literals, gefolgt von einer Liste aus Verbundelementen. Jedem dieser Elemente kann ein zugehöriger Schlüssel vorausgehen.

CompositeLit  = LiteralType LiteralValue .
LiteralType   = StructType | ArrayType | "[" "..." "]" ElementType |
                SliceType | MapType | TypeName .
LiteralValue  = "{" [ ElementList [ "," ] ] "}" .
ElementList   = KeyedElement { "," KeyedElement } .
KeyedElement  = [ Key ":" ] Element .
Key           = FieldName | Expression | LiteralValue .
FieldName     = identifier .
Element       = Expression | LiteralValue .

Der Ursprungstyp des Literals muss ein Struktur-, Array-, Slice- oder ein Map-Typ sein (Die Grammatik verlangt diese Einschränkung, solange der Typ nicht in Form des Typnamens gegeben wird). Die Typen der Schlüssel und der Elemente müssen zu den jeweiligen Feld-, Element- und Schlüsseltypen zuweisbar sein; es erfolgt keine Konversion. Der Schlüssel wird als Feldname für Struktur-Literale, als Index für Array- und Slice-Literale sowie als Schlüssel für Map-Literale interpretiert. Alle Elemente eines Map-Literals müssen einen Schlüssel haben. Es ist ein Fehler, wenn mehrere Elemente mit demselben Feldnamen oder demselben konstanten Schlüsselwert festgelegt werden. Zu nicht-konstanten Map-Schlüsseln steht etwas im Abschnitt Auswertungsreihenfolge.

Für Struktur-Literale gelten die folgenden Regeln:

Sind diese Deklarationen gegeben:

type Point3D struct { x, y, z float64 }
type Line struct { p, q Point3D }

, so ist folgendes möglich:

origin := Point3D{}                            // Nullwert für Point3D
line := Line{origin, Point3D{y: -4, z: 12.3}}  // Nullwert für line.q.x

Für Array- und Slice-Literale gelten folgende Regeln:

Abgreifen der Adresse eines Verbundliterals erzeugt einen Zeiger auf eine eindeutige Variable, die mit dem Literalwert initialisiert wird.

var pointer *Point3D = &Point3D{y: 1000}

Die Länge eines Array-Literals ist die im Literaltyp angegebene. Wenn durch das Literal weniger Elemente angegeben werden, als die Länge vorgibt, so werden die fehlenden Elemente auf den Nullwert ihres Typs gesetzt. Es ist ein Fehler, Elemente mit Indexwerten außerhalb des Indexbereichs anzugeben. Die Schreibweise ... setzt die Arraylänge auf den höchsten Elementindex plus eins.

buffer := [10]string{}             // len(buffer) == 10
intSet := [6]int{1, 2, 3, 5}       // len(intSet) == 6
days := [...]string{"Sat", "Sun"}  // len(days) == 2

Ein Slice-Literal beschreibt das gesamte zugrundeliegende Array-Literal. Also sind sowohl Länge als auch Kapazität des Slice-Literals gleich dem höchsten Elementindex plus eins. Ein Slice-Literal hat die Form:

[]T{x1, x2, … xn}

und ist eine verkürzte Schreibweise für das Aufschneiden [englisch: to slice, A.d.Ü.] eines Arrays:

tmp := [n]T{x1, x2, … xn}
tmp[0 : n]

Innerhalb eines Verbundliterals vom Array-, Slice- oder Map-Typ T dürfen Elemente oder Schlüssel von Maps, die wiederum Verbundliterale sind, den Literaltyp weglassen, wenn er gleich dem Element- oder Schlüsseltyp T ist. Ähnlich dürfen Elemente oder Schlüssel, die Adressen von Verbundliteralen sind, &T weglassen, wenn der Element- oder Schlüsseltyp *T ist.

[...]Point{{1.5, -3.5}, {0, 0}}   // gleich [...]Point{Point{1.5, -3.5}, Point{0, 0}}
[][]int{{1, 2, 3}, {4, 5}}        // gleich [][]int{[]int{1, 2, 3}, []int{4, 5}}
[][]Point{{{0, 1}, {1, 2}}}       // gleich [][]Point{[]Point{Point{0, 1}, Point{1, 2}}}
map[string]Point{"orig": {0, 0}}  // gleich map[string]Point{"orig": Point{0, 0}}
map[Point]string{{0, 0}: "orig"}  // gleich map[Point]string{Point{0, 0}: "orig"}

type PPoint *Point
[2]*Point{{1.5, -3.5}, {}}        // gleich [2]*Point{&Point{1.5, -3.5}, &Point{}}
[2]PPoint{{1.5, -3.5}, {}}        // gleich [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}

Eine Mehrdeutigkeit entsteht, wenn ein Verbundliteral, in der Typnamenschreibweise, als Operand zwischen dem Schlüsselwort und der öffnenden geschweiften Klammer einer If-, For- oder Switch-Anweisung auftaucht, und nicht in runde, eckige oder geschweifte Klammern eingeschlossen ist. In diesem seltenen Fall würde die öffnende geschweifte Klammer des Literals fälschlicherweise als Einleitung eines Anweisungsblocks interpretiert werden. Um die Mehrdeutigkeit aufzuheben, muss das Verbundliteral in runden Klammern stehen:

if x == (T{a,b,c}[i]) { … }  // oder
if (x == T{a,b,c}[i]) { … }

Es folgen Beispiele für gültige Array-, Slice- und Map-Literale:

// Liste aus Primzahlen
primes := []int{2, 3, 5, 7, 9, 2147483647}

// vowels[ch] ist true, wenn ch ein Selbstlaut ist
vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}

// Das Array [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1}

// Frequenzen in Hz für die wohltemperierte Skala (A4 = 440Hz)
noteFrequency := map[string]float32{
    "C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
    "G0": 24.50, "A0": 27.50, "B0": 30.87,
}

Funktionsliterale

Ein Funktionsliteral steht für eine anonyme Funktion.

FunctionLit = "func" Signature FunctionBody .
func(a, b int, z float64) bool { return a*b < int(z) }

Ein Funktionsliteral kann einer Variablen zugewiesen oder direkt aufgerufen werden.

f := func(x, y int) int { return x + y }
func(ch chan int) { ch <- ACK }(replyChan)

Funktionsliterale sind Funktionsabschlüsse [englisch: closure, A.d.Ü.]: sie können auf Variablen verweisen, die in einer sie umschließenden Funktion definiert sind. Die umschließende Funktion und das Funktionsliteral teilen sich die Variablen; diese leben solange sie erreichbar sind.

Primäre Ausdrücke

Primäre Ausdrücke sind die Operanden für unäre und binäre Ausdrücke.

PrimaryExpr =
	Operand |
	Conversion |
	MethodExpr |
	PrimaryExpr Selector |
	PrimaryExpr Index |
	PrimaryExpr Slice |
	PrimaryExpr TypeAssertion |
	PrimaryExpr Arguments .

Selector       = "." identifier .
Index          = "[" Expression "]" .
Slice          = "[" [ Expression ] ":" [ Expression ] "]" |
                 "[" [ Expression ] ":" Expression ":" Expression "]" .
TypeAssertion  = "." "(" Type ")" .
Arguments      = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
x
2
(s + ".txt")
f(3.1415, true)
Point{1, 2}
m["foo"]
s[i : j + 1]
obj.color
f.p[i].x()

Selektoren

Für einen primären Ausdruck x, der kein Paketname ist, bezeichnet der Selektorausdruck:

x.f

das Feld oder die Methode f des Werts, der mit x (oder manchmal mit *x; siehe unten) bezeichnet ist. Der Bezeichner f wird (Feld- oder Methoden-) Selektor genannt; er darf nicht der Leere Bezeichner sein. Der Typ des Ausdrucks ist der Typ von f. Für den Fall, dass x ein Paketname ist, siehe: Abschnitt "Qualifizierte Bezeichner".

Ein Selektor f kann ein Feld oder eine Methode f eines Typs T bezeichnen, oder ein Feld oder Methode f eines in T enthaltenen eingebetteten Felds. Man nennt die Anzahl eingebetteter Felder, die bis zum Erreichen von f zu durchschreiten ist, seine Tiefe in T. Die Tiefe eines in T deklarierten Feldes/einer Methode f ist null. Die Tiefe eines in einem eingebetteten Feld A in T deklarierten Feldes/einer Methode f ist die Tiefe von f in A plus Eins.

Die folgenden Regeln gelten für Selektoren.

  1. Wenn T kein Zeiger- oder Interface-Typ ist, dann bezeichnet x.f für einen Wert x vom Typ T oder *T ein Feld oder eine Methode in der geringsten Tiefe, in der ein solches f auftritt. Wenn es nicht genau ein f mit der geringsten Tiefe gibt, dann ist der Selektorausdruck nicht erlaubt.
  2. Wenn ein Wert x vom Typ I ist, und I ein Interface-Typ, dann bezeichnet x.f die Methode mit dem Namen f des dynamischen Werts von x. Enthält die Methodenmenge von I keine Methode mit dem Namen f, so ist x.f nicht erlaubt.
  3. Ausnahme: Wenn der Typ von x ein definierter Zeigertyp und (*x).f ein gültiger Selektorausdruck ist, der ein Feld, nicht eine Methode) bezeichnet, dann ist x.f die Kurzform für (*x).f.
  4. In allen anderen Fällen ist x.f nicht erlaubt.
  5. Wenn x von einem Zeiger-Typ ist, den Wert nil hat, und x.f das Feld einer Struktur bezeichnet, dann führen Zuweisungen oder Auswertungen von x.f zu einem Laufzeitfehler.
  6. Wenn x von einem Interface-Typ ist und den Wert nil hat, so führt der Aufruf oder das Auswerten der Methode x.f zu einem Laufzeitfehler.

Sind zum Beispiel folgende Deklarationen gegeben:

type T0 struct {
    x int
}

func (*T0) M0()

type T1 struct {
    y int
}

func (T1) M1()

type T2 struct {
    z int
    T1
    *T0
}

func (*T2) M2()

type Q *T2

var t T2     // mit t.T0 != nil
var p *T2    // mit p != nil und (*p).T0 != nil
var q Q = p

so kann man schreiben:

t.z          // t.z
t.y          // t.T1.y
t.x          // (*t.TO).x

p.z          // (*p).z
p.y          // (*p).T1.y
p.x          // (*(*p).T0).x

q.x          // (*(*q).T0).x        (*q).x ist ein gültiger Feldselektor

p.M0()       // ((*p).T0).M0()      M0 erwartet *T0 Empfänger
p.M1()       // ((*p).T1).M1()      M1 erwartet T1 Empfänger
p.M2()       // p.M2()              M2 erwartet *T2 Empfänger
t.M2()       // (&t).M2()           M2 erwartet *T2 Empfänger, siehe Abschnitt "Funktionsaufrufe"

Methodenausdrücke

Wenn M Teil der Methodenmenge eines Typs T ist, dann ist T.M eine Funktion, die wie eine reguläre Funktion mit denselben Argumenten wie M aufgerufen werden kann, mit einem zusätzlichen Argument vorneweg, welches den Empfänger der Methode angibt.

MethodExpr    = ReceiverType "." MethodName .
ReceiverType  = Type .

Gegeben sei ein Struktur-Typ T mit zwei Methoden, nämlich Mv mit einem Empfänger vom Typ T und Mp mit einem Empfänger vom Typ *T:

type T struct {
    a int
}
func (tv  T) Mv(a int) int         { return 0 }  // Empfänger ist ein Wert
func (tp *T) Mp(f float32) float32 { return 1 }  // Empfänger ist ein Zeiger

var t T

Der Ausdruck

T.Mv

ergibt eine Funktion gleichwertig zu Mv, nur mit einem expliziten Empfänger als erstem Argument; ihre Signatur ist:

func(tv T, a int) int

Diese Funktion kann ganz normal, mit einem expliziten Empfänger, gerufen werden; diese fünf Aufrufe sind also gleichwertig:

t.Mv(7)
T.Mv(t, 7)
(T).Mv(t, 7)
f1 := T.Mv; f1(t, 7)
f2 := (T).Mv; f2(t, 7)

Analog ergibt der Ausdruck:

(*T).Mp

einen Funktionswert, der für Mp steht, mit der Signatur:

func(tp *T, f float32) float32

Aus einer Methode mit einem Wert als Empfänger lässt sich eine Funktion mit einem Zeiger als Empfänger ableiten, so dass:

(*T).Mv

einen Funktionswert ergibt, der für Mv steht, mit der Signatur:

func(tv *T, a int) int

Eine solche Funktion löst den Zeiger auf, um daraus den Wert zu ermitteln, der als Empfänger an die zugrundeliegende Methode weitergereicht wird; der Wert, dessen Adresse beim Funktionsaufruf übergeben wird, wird durch die Methode nicht überschrieben.

Übrig bleibt der Fall einer Wert empfangenden Funktion anstelle einer Zeiger empfangenden Methode: das ist nicht erlaubt, da Zeiger empfangende Methoden nicht Teil der Methodenmenge des Werttyps sind.

Funktionswerte, die von Methoden abgeleitet sind, werden wie Funktionen gerufen; der Empfänger wird dem Aufruf als erstes Argument mitgegeben. Für ein gegebenes f := T.Mv wird also f durch f(t, 7) und nicht durch t.f(7) gerufen. Um eine Funktion zu konstruieren, die den Empfänger einbindet, benutzt man ein Funktionsliteral oder einen Methodenwert.

Es ist erlaubt, einen Funktionswert von der Methode eines Interface-Typs abzuleiten. Die sich ergebende Funktion bekommt explizit einen Empfänger dieses Interface-Typs.

Methodenwerte

Wenn der Ausdruck x vom statischen Typ T ist und M zur Methodenmenge von T gehört, so nennt man x.M einen Methodenwert. Der Methodenwert x.M ist ein Funktionswert, der mit denselben Argumenten wie der Methodenaufruf x.M aufrufbar ist. Der Ausdruck x wird während der Auswertung des Methodenwerts ausgewertet und gesichert; die gesicherte Kopie wird dann als Empfänger aller Aufrufe benutzt, die eventuell später ausgeführt werden.

Der Typ T kann ein Interface- oder ein anderer Typ sein.

Stellen Sie sich — wie oben in der Erörterung der Methoden-Ausdrücke — einen Struktur-Typ vor, mit zwei Methoden, nämlich Mv mit dem Empfänger vom Typ T und Mp mit dem Empfänger vom Typ *T:

type T struct {
	a int
}
func (tv  T) Mv(a int) int         { return 0 }  // Wert ist Empfänger
func (tp *T) Mp(f float32) float32 { return 1 }  // Zeiger ist Empfänger

var t T
var pt *T
func makeT() T

Der Ausdruck

t.Mv

ergibt einen Funktionswert vom Typ

func(int) int

Diese beiden Aufrufe sind gleichwertig:

t.Mv(7)
f := t.Mv; f(7)

Ähnlich, ergibt der Ausdruck

pt.Mp

einen Funktionswert vom Typ

func(float32) float32

Wie bei Selektoren wird beim Benutzen einer Methode, die einen Wert als Empfänger hat und keine Interface-Methode ist, ein Zeiger als Empfänger automatisch aufgelöst [dereferenziert, A.d.Ü.]: pt.Mv ist gleichwertig zu (*pt).Mv.

Wie bei Methodenaufrufen wird beim Benutzen einer Methode, die einen Zeiger als Empfänger hat und keine Interface-Methode ist, von einem adressierbaren Wert automatisch die Adresse genommen [referenziert, A.d.Ü.]: t.Mp ist gleichwertig zu (&t).Mp.

f := t.Mv; f(7)   // wie t.Mv(7)
f := pt.Mp; f(7)  // wie pt.Mp(7)
f := pt.Mv; f(7)  // wie (*pt).Mv(7)
f := t.Mp; f(7)   // wie (&t).Mp(7)
f := makeT().Mp   // verboten: Ergebnis von makeT() ist nicht adressierbar

Über diese Beispiele ohne Interfaces-Typen hinaus, ist es erlaubt, einen Methodenwert von dem Wert eines Interface-Typs zu erzeugen:

var i interface { M(int) } = myVal
f := i.M; f(7)  // like i.M(7)

Indexausdrücke

Ein primärer Ausdruck der Form:

a[x]

bezeichnet dasjenige Element von Array, Array-Zeiger, Slice, String oder Map a, das durch x indiziert wird. Der Wert x wird Index oder Map-Schlüssel genannt, je nachdem. Es gelten die folgenden Regeln:

Wenn a keine Map ist, git:

Für a vom Array-Typ A gilt:

Wenn a Zeiger auf einen Array-Typ ist, gilt:

Für a vom Slice-Typ S gilt:

Wenn a ein String-Typ ist, gilt:

Wenn a vom Map-Typ M ist, gilt:

Andere a[x] sind nicht erlaubt.

Ein Indexausdruck einer Map a vom Typ map[K]V, der in einer Zuweisung oder Initialisierung in der besonderen Form:

v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]

benutzt wird, erzeugt einen zusätzlichen typfreien Boole'schen Wert. Der Wert von ok ist true, wenn der Schlüssel x in der Map existiert, andernfalls false.

Zuweisen zu einem Element einer nil-Map führt zu einem Laufzeitfehler.

Slice-Ausdrücke

Slice-Ausdrücke erzeugen eine Teilkette [englisch: substring, A.d.Ü.] oder ein Slice aus einem String, Array, Zeiger auf ein Array oder Slice. Es gibt zwei Varianten: die einfache Form, die eine untere und eine obere Grenze festlegen, und die vollständige Form, die außerdem eine Kapazitätsgrenze angibt.

Einfache Slice-Ausdrücke

Für einen String, ein Array, einen Zeiger auf ein Array oder ein Slice a erzeugt:

a[low : high]

eine Teilkette oder ein Slice. Die Indices low und high bestimmen welche Elemente des Operanden a zum Ergebnis gehören. Der Index des Ergebnisses beginnt bei 0, die Länge ist high - low. Nach dem Aufschneiden [englisch: slicing, A.d.Ü.] des Arrays a:

a := [5]int{1, 2, 3, 4, 5}
s := a[1:4]

hat das Slice s den Typ []int, die Länge 3, die Kapazität 4 und die Elemente:

s[0] == 2
s[1] == 3
s[2] == 4

Für mehr Komfort ist es erlaubt, Indices wegzulassen. Fehlt low, dann wird 0, fehlt high, dann wird die Länge des aufgeschnittenen Operanden angenommen:

a[2:]  // gleich a[2 : len(a)]
a[:3]  // gleich a[0 : 3]
a[:]   // gleich a[0 : len(a)]

Wenn a ein Zeiger auf ein Array ist, dann ist a[low : high] die Abkürzung für (*a)[low : high].

Für Arrays oder Strings sind die Indices bereichszugehörig, wenn 0 <= low <= high <= len(a), andernfalls sind sie bereichsfremd. Für Slices ist die Obergrenze für den Index die Kapazität cap(a) anstelle der Länge. Ein konstanter Index muss nicht-negativ und durch einen Wert vom Typ int darstellbar sein; für Arrays und konstante Strings müssen konstante Indices ebenfalls bereichszugehörig sein. Wenn beide Indices konstant sind, muss gelten: low <= high. Wenn zur Laufzeit die Indices bereichsfremd sind, dann führt das zu einem Laufzeitfehler.

Typfreie Strings ausgenommen — wenn der aufgeschnittene Operand ein String oder Slice ist, dann ist das Ergebnis des Aufschneidens ein nicht-konstanter Wert vom gleichen Typ. Bei typfreien String-Operanden ist das Ergebnis ein nicht-konstanter vom Typ String. Ist der aufgeschnittene Operand ein Array, dann muss er adressierbar sein, und das Ergebnis des Aufschneidens ist ein Slice mit dem gleichen Elementtyp wie das Array.

Ist der aufgeschnittene Operand eines gültigen Slice-Ausdrucks ein nil-Slice, so ist auch das Ergebnis ein nil-Slice. Ansonsten, wenn das Ergebnis ein Slice ist, teilen sich Ergebnis und Operand das zugrundeliegende Array.

Vollständige Slice-Ausdrücke

Für ein Array, einen Zeiger auf ein Array oder ein Slice a (aber nicht für einen String) erzeugt:

a[low : high : max]

ein Slice desselben Typs, der gleichen Länge und den gleichen Elementen wie der einfache Slice-Ausdruck a[low : high]. Zusätzlich legt es die Kapazität des sich ergebenden Slice als max - low fest. Nur der erste Index darf weggelassen werden; es wird dann 0 angenommen. Nach dem Aufschneiden des Arrays a:

a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]

ist das Slice t vom Typ []int, hat die Länge 2, die Kapazität 4 und die Elemente:

t[0] == 2
t[1] == 3

Ähnlich wie bei einfachen Slice-Ausdrücken gilt: Wenn a ein Zeiger auf ein Array ist, dann ist a[low : high : max] die Abkürzung für (*a)[low : high : max]. Ist der aufgeschnittene Operand ein Array, dann muss er adressierbar sein.

Die Indices sind bereichszugehörig, wenn 0 <= low <= high <= max <= cap(a), sonst sind sie bereichsfremd. Ein konstanter Index muss nicht-negativ sein und darstellbar als Wert vom Typ int; für Arrays und konstante Strings müssen konstante Indices ebenfalls bereichszugehörig sein. Wenn mehrere Indices konstant sind, müssen diese im Verhältnis zueinander bereichszugehörig sein. Wenn zur Laufzeit die Indices bereichsfremd sind, dann führt das zu einem Laufzeitfehler.

Typzusicherungen

Für einen Ausdruck x, der ein Interface-Typ ist, und einen Typ T sichert der primäre Ausdruck:

x.(T)

zu, dass x nicht nil ist und dass der Wert, der in x gespeichert ist, vom Typ T ist. Die Schreibweise x.(T) nennt man eine Typzusicherung [englisch: type assertion, A.d.Ü.].

Genauer gesagt, sichert x.(T) für ein T, das kein Interface-Typ ist, zu, dass der dynamische Typ von x gleich T ist. In dem Fall muss T den (Interface-)Typ von x implementieren; andernfalls wäre die Zusicherung ungültig, da es für x nicht möglich ist, einen Wert vom Typ T aufzunehmen. Wenn T ein Interface-Typ ist, dann sichert x.(T) zu, dass der dynamische Typ von x das Interface T implementiert.

Wenn die Typzusicherung zutrifft, dann ist der Wert des Ausdrucks der Wert, der in x gespeichert ist und sein Typ ist T. Wenn die Typzusicherung nicht zutrifft, tritt ein Laufzeitfehler auf. Anders gesagt, obwohl der dynamische Typ von x erst zur Laufzeit bekannt ist, weiß man, dass der Typ von x.(T) in einem korrekten Programm T ist.

var x interface{} = 7  // x hat den dynamischen Typ int und den Wert 7
i := x.(int)           // i ist vom Typ int und hat den Wert 7

type I interface { m() }

func f(y I) {
    s := y.(string)    // verboten: string implementiert nicht I (Methode m fehlt)
    r := y.(io.Reader) // r hat Typ io.Reader und der dynamische Typ von y muss sowohl 
                       // I als auch io.Reader implementieren
    …
}

Wird eine Typzusicherung in einer Zuweisung oder Initialisierung in der Form:

v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok T1 = x.(T)

benutzt, so erzeugt das einen zusätzlichen typfreien Boole'schen Wert. Der Wert von ok ist true, wenn die Zusicherung zutrifft. Andernfalls ist er false, und der Wert von v ist der Nullwert des Typs T. In diesem Fall gibt es keinen Laufzeitfehler.

Funktionsaufrufe

Ist ein Ausdruck f eines Funktionstyps F vorgegeben, dann ruft:

f(a1, a2, … an)

die Funktion f mit den Argumenten a1, a2, … an auf. Mit einer speziellen Ausnahme müssen Argumente einwertige Ausdrücke sein, die den Parametertypen von F zuweisbar sein müssen, und die vor dem Funktionsaufruf ausgewertet werden. Der Typ des Ausdrucks ist der Ergebnistyp von F. Ein Methodenaufruf ist ähnlich, nur dass die Methode als Selektor eines Werts vom Empfängertyp der Methode angegeben wird:

math.Atan2(x, y)  // Funktionsaufruf
var pt *Point
pt.Scale(3.5)     // Methodenaufruf mit Empfänger pt

Bei einem Funktionsaufruf werden der Funktionswert und die Argumente in der üblichen Reihenfolge ausgewertet. Nach dieser Auswertung werden die Aufrufparameter als Werte [englisch: by value, A.d.Ü.] an die Funktion übergeben, und die Funktion beginnt zu arbeiten. Die Ergebnisparameter der Funktion werden bei der Rückkehr als Werte an die rufende Funktion zurückgegeben.

Ein nil-Funktionswert führt zu einem Laufzeitfehler.

Als ein Spezialfall kann ein Aufruf f(g(parameters_of_g)) die Ergebniswerte von g der Reihe nach an die Parameter von f binden, wenn die Ergebniswerte der Funktion oder Methode g in Anzahl und Zuweisbarkeit den Parametern der Funktion oder Methode f entsprechen. Der Aufruf von f darf keine anderen Parameter als den Aufruf von g enthalten und g muss mindestens einen Ergebniswert haben. Wenn f als letzten Parameter ... hat, dann werden dem alle Ergebniswerte von g zugewiesen, die nach der Zuweisung zu den regulären Parametern übrig bleiben.

func Split(s string, pos int) (string, string) {
    return s[0:pos], s[pos:]
}

func Join(s, t string) string {
    return s + t
}

if Join(Split(value, len(value)/2)) != value {
    log.Panic("fehlgeschlagen")
}

Ein Methodenaufruf x.m() ist gültig, wenn m in der Methodenmenge des Typs von x enthalten ist und die Argumentliste der Parameterliste von m zugewiesen werden kann. Wenn x adressierbar ist und die Methodenmenge von &x m enthält, dann ist x.m() die Abkürzung für (&x).m():

var p Point
p.Scale(3.5)

Es gibt keinen eigenen Methodentyp und es gibt keine Methodenliterale.

Argumentübergabe an den ...-Parameter

Ist f eine variadische Funktion mit einem Parameter p vom Typ ...T am Ende, so ist innerhalb von f der Typ von p gleichwertig zum Typ []T. Wird f ohne Argumente für p aufgerufen, dann wird nil als Wert für p übergeben. Andernfalls ist der übergebene Wert ein neues Slice vom Typ []T, dessen aufeinanderfolgende Elemente die eigentlichen Argumente sind, und die alle dem Typ T zuweisbar sein müssen. Länge und Kapazität des Slice ist also die Anzahl der Argumente, die an den letzten Parameter gebunden sind, und die kann für jeden Aufruf verschieden sein.

Für die folgende Funktion und ihre Aufrufe:

func Greeting(prefix string, who ...string)
Greeting("niemand")
Greeting("Hallo:", "Joe", "Anna", "Eileen")

wird innerhalb von Greeting das who den Wert nil im ersten Aufruf, und []string{"Joe", "Anna", "Eileen"} im zweiten Aufruf haben.

Ist das letzte Argument einem Slice-Typ []T zuweisbar, so darf es unverändert als ein Wert für ...T übergeben werden, wenn auf das Argument ... folgt. In diesem Fall wird kein neues Slice erzeugt.

Für das Slice s und den folgenden Funktionsaufruf:

s := []string{"James", "Jasmine"}
Greeting("Tschüs:", s...)

wird innerhalb von Greeting das who denselben Wert wie s mit demselben zugrundeliegenden Array haben.

Operatoren

Operatoren verbinden Operanden zu Ausdrücken.

Expression = UnaryExpr | Expression binary_op Expression .
UnaryExpr  = PrimaryExpr | unary_op UnaryExpr .

binary_op  = "||" | "&&" | rel_op | add_op | mul_op .
rel_op     = "==" | "!=" | "<" | "<=" | ">" | ">=" .
add_op     = "+" | "-" | "|" | "^" .
mul_op     = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" .

unary_op   = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .

Die Vergleiche werden woanders behandelt. Bei den übrigen binären Operationen müssen die Typen der Operanden identisch sein, außer wenn es sich um Schiebe-Operationen [englisch: shifts, A.d.Ü.] handelt oder typfreie Konstanten beteiligt sind. Operationen, an denen nur Konstanten beteiligt sind, sind im Abschnitt "Konstantenausdrücke" beschrieben.

Außer bei Schiebe-Operationen wird eine Konstante zu dem Typ des jeweils anderen Operanden konvertiert, wenn sie eine typfreie Konstante ist und die jeweils andere nicht.

The right operand in a shift expression must have unsigned integer type or be an untyped constant representable by a value of type uint.

Der rechte Operand in einem Schiebe-Ausdruck muss eine Ganzzahl ohne Vorzeichen sein oder eine typfreie Konstante, die durch einen Wert vom Typ uint darstellbar ist. Ist der linke Operand eines nicht-konstanten Schiebe-Ausdrucks eine typfreie Konstante, so wird er zuerst zu dem Typ konvertiert, den er annehmen würde, wenn der Schiebe-Ausdruck nur durch seinen linken Operanden ersetzt würde.

var s uint = 33
var i = 1<<s                  // 1 ist vom Typ int
var j int32 = 1<<s            // 1 ist vom Typ int32; j == 0
var k = uint64(1<<s)          // 1 ist vom Typ uint64; k == 1<<33
var m int = 1.0<<s            // 1.0 ist vom Typ int; m == 0 wenn int 32 Bit lang ist
var n = 1.0<<s == j           // 1.0 ist vom Typ int32; n == true
var o = 1<<s == 2<<s          // 1 and 2 sind vom Typ int; o == true, wenn ints 32 Bit lang sind
var p = 1<<s == 1<<33         // verboten, wenn int 32 Bit lang ist: 1 ist vom Typ int, aber 1<<33 läuft über
var u = 1.0<<s                // verboten: 1.0 ist vom Typ float64, kann nicht geschoben werden
var u1 = 1.0<<s != 0          // verboten: 1.0 ist vom Typ float64, kann nicht geschoben werden
var u2 = 1<<s != 1.0          // verboten: 1 ist vom Typ float64, kann nicht geschoben werden
var v float32 = 1<<s          // verboten: 1 ist vom Typ float32, , kann nicht geschoben werden
var w int64 = 1.0<<33         // 1.0<<33 ist ein konstanter Schiebe-Ausdruck
var x = a[1.0<<s]             // 1.0 ist vom Typ int; x == a[0], wenn int 32 Bit groß ist
var a = make([]byte, 1.0<<s)  // 1.0 ist vom Typ int; len(a) == 0, wenn int 32 Bit groß ist

Rangfolge der Operatoren

Unäre Operatoren haben die höchste Priorität. Da die Operatoren ++ und -- Anweisungen und keine Ausdrücke bilden, fallen sie aus der Operatorhierarchie heraus. Deshalb ist *p++ das gleiche wie (*p)++.

Es gibt fünf Prioritätsstufen. Die Multiplikationsoperatoren binden am stärksten, gefolgt von den Additionsoperatoren, den Vergleichsoperatoren, dem && (logisches UND), und zuletzt dem || (logisches ODER):

Priorität     Operator
    5             *  /  %  <<  >>  &  &^
    4             +  -  |  ^
    3             ==  !=  <  <=  >  >=
    2             &&
    1             ||

Binäre Operatoren der gleichen Priorität werden von links nach rechts verbunden. Zum Beispiel ist x / y * z dasgleiche wie (x / y) * z.

+x
23 + 3*x[i]
x <= f()
^a >> b
f() || g()
x == y+1 && <-chanPtr > 0

Arithmetische Operatoren

Arithmetische Operatoren werden auf numerische Werte angewendet und führen zu einem Ergebnis desselben Typs wie dem des ersten Operanden. Die vier üblichen arithmetischen Operatoren (+, -, * und /) werden auf die Typen der Ganz-, Gleitkomma- und der Komplexzahlen angewendet; + kann auch auf Strings angewendet werden. Die bitweise logischen und die Schiebe-Operatoren können nur auf Ganzzahlen angewendet werden.

+    Summe                   Ganz-, Gleitkomma-, Komplexzahlen, Strings
-    Differenz               Ganz-, Gleitkomma-, Komplexzahlen
*    Produkt                 Ganz-, Gleitkomma-, Komplexzahlen
/    Quotient                Ganz-, Gleitkomma-, Komplexzahlen
%    Rest                    Ganzzahlen

&    Bitweise UND            Ganzzahlen
|    Bitweise ODER           Ganzzahlen
^    Bitweise XODER          Ganzzahlen
&^   Bit löschen (UND NICHT) Ganzzahlen

<<   nach links schieben     Ganzzahlen << Ganzzahlen ohne Vorzeichen
>>   nach rechst schieben    Ganzzahlen >> Ganzzahlen ohne Vorzeichen

Ganzzahloperatoren

Für zwei Ganzzahlwerte x und y stehen der ganzzahlige Quotient q = x / y und der Rest r = x % y in folgender Beziehung zueinander:

x = q*y + r  und  |r| < |y|

wobei x / y nach dem Komma abgeschnitten wird ("truncated division").

 x     y     x / y     x % y
 5     3       1         2
-5     3      -1        -2
 5    -3      -1         2
-5    -3       1        -2

Es gibt eine Ausnahme zu dieser Regel: Wenn der Dividend x der negativst mögliche Wert des Ganzzahltyps von x ist, dann ist der Quotient q = x / -1 gleich x (und r = 0), und zwar wegen des Ganzzahlüberlaufs des Zweierkomplements:

                         x, q
int8                     -128
int16                  -32768
int32             -2147483648
int64    -9223372036854775808

Ist der Divisor eine Konstante, dann darf er nicht null sein. Ist der Divisor zur Laufzeit null ist, dann tritt ein Laufzeitfehler auf. Ist der Dividend nicht-negativ und der Divisor eine konstante Potenz von 2, so darf die Division durch Nach-rechts-Schieben und das Bestimmen des Rest durch Bitweise-UND ersetzt werden.

 x     x / 4     x % 4     x >> 2     x & 3
 11      2         3         2          3
-11     -2        -3        -3          1

Die Schiebeoperatoren verschieben den linken Operanden um die Schiebeanzahl, die durch den rechten Operanden angegeben ist. Sie implementieren arithmetisches Schieben, wenn der linke Operand eine Ganzzahl mit Vorzeichen, ein logisches Schieben, wenn er eine Ganzzahl ohne Vorzeichen ist. Es gibt keine Obergrenze für die Schiebeanzahl. Verschieben um die Schiebeanzahl n verhält sich als ob der linke Operand n-mal um 1 verschoben wird. Das Ergebnis von x << 1 ist dasselbe wie von x*2, das von x >> 1 dasselbe wie x/2 abgeschnitten in Richtung Minus-Unendlich.

Für Ganzzahloperanden sind die unären Operatoren +, - und ^ wie folgt definiert:

+x                     wie 0 + x
-x   Negation          wie 0 - x
^x   Bit-Komplement    wie m ^ x wobei: m "hat alle Bits auf 1" für x ohne Vorzeichen
                                 oder   m == -1                 für x mit  Vorzeichen

Ganzzahlüberlauf

Für Ganzzahlwerte ohne Vorzeichen werden die Operationen +, -, * und << modulo 2n berechnet, wobei n die Länge (in Bit) des Ganzzahltyps ohne Vorzeichen ist. Vereinfacht gesagt verwerfen diese Ganzzahloperationen ohne Vorzeichen die überlaufenden hohen Bits; Programme dürfen darauf zählen, dass die Null wieder durchlaufen wird.

Für Ganzzahlwerte mit Vorzeichen dürfen die Operationen +, -, *, / und << überlaufen; es gibt ein Ergebnis, und das ist eindeutig definiert durch die Darstellung als Ganzzahl mit Vorzeichen, durch die Operation und durch die Operanden. Überlauf erzeugt keinen Laufzeitfehler. Ein Compiler darf nicht unter der Prämisse optimieren, es gäbe keinen Überlauf. Beispielsweise wäre es falsch anzunehmen, dass x < x + 1 immer wahr sei.

Gleitkommaoperatoren

Für Gleitkomma- und komplexe Zahlen ist +x dasgleiche wie x, während -x die Negation von x ist. Das Ergebnis einer Gleitkomma- oder Komplexdivision durch Null ist nicht über den Standard IEEE-745 hinaus festgelegt; ob ein Laufzeitfehler auftritt, hängt von der Go-Implementierung ab.

Eine Implementierung darf mehrere Gleitkommaoperationen, auch über mehrere Anweisungen hinweg, in einer einzigen Operation fusionieren, und dabei ein Ergebnis produzieren, das von dem Wert abweicht, der durch Einzeloperationen mit jeweils anschließendem Runden erreicht würde. Konversion eines Gleitkommatyps rundet auf die Präzision des Zieltyps und verhindert damit eine Fusion, die solches Runden verwerfen würde.

Beispielsweise bietet manche Prozessorarchitektur eine "fusionierte Multiplizier und Addier"-Anweisung (FMA), die x*y + z ohne Runden des Zwischenergebnisses x*y berechnet. Folgende Beispiele zeigen, wann eine Go-Implementierung diese Anweisung benutzen kann.

// FMA zum Berechnen von r erlaubt, weil x*y nicht explizit gerundet wird:
r  = x*y + z
r  = z;   r += x*y
t  = x*y; r = t + z
*p = x*y; r = *p + z
r  = x*y + float64(z)

// FMA zum Berechnen von r nicht erlaubt, weil das das Runden von x*y ausließe:
r  = float64(x*y) + z
r  = z; r += float64(x*y)
t  = float64(x*y); r = t + z

String-Verkettung

Strings können mit dem Operator + oder mit dem Zuweisungsoperator += verkettet werden:

s := "Hallo" + string(c)
s += " und tschüs"

String-"Addition" erzeugt einen neuen String, indem die Operanden verkettet werden.

Vergleichsoperatoren

Vergleichsoperatoren vergleichen zwei Operanden und ergeben einen typfreien Boole'schen Wert.

==    gleich
!=    ungleich
<     kleiner
<=    kleiner oder gleich
>     größer
>=    größer oder gleich

Bei jedem Vergleich muss der erste Operand dem Typ des zweiten Operanden zuweisbar sein, oder umgekehrt.

Die Gleichheitsoperatoren == und != können auf Operanden angewandt werden, die vergleichbar sind. Die ordnenden Operatoren <, <=, > und >= können auf Operanden angewandt werden, die geordnet sind. Diese Begriffe sowie die Vergleichsergebnisse sind wie folgt definiert:

Der Vergleich zweier Interface-Werte von identischem dynamischen Typ führt dann zu einem Laufzeitfehler, wenn Werte dieses Typs nicht vergleichbar sind. Das gilt auch, wenn nicht Interface-Werte direkt verglichen werden, sondern Arrays aus Interface-Werten oder Strukturen mit Feldern mit Interface-Werten.

Slice-, Map- oder Funktions-Werte sind nicht vergleichbar. Allerdings dürfen — als Spezialfall — Slice-, Map- oder Funktions-Werte mit dem vordeklarierten Bezeichner nil verglichen werden. Der erlaubte Vergleich von Zeiger- Kanal- und Interface-Werten mit nil folgt aus den oben aufgeführten Regeln.

const c = 3 < 4  // c ist die typfreie Boole'sche Konstante true

type MyBool bool
var x, y int
var (
    // Das Ergebnis eines Vergleichs ist eine typfreies Boole'sche Variable.
    // Es gelten die üblichen Regeln für Zuweisungen.
    b3        = x == y  // b3 ist vom Typ bool
    b4 bool   = x == y  // b4 ist vom Typ bool
    b5 MyBool = x == y  // b5 ist vom Typ MyBool
)

Logische Operatoren

Logische Operatoren werden auf Boole'sche Werte angewendet und ergeben ein Ergebnis vom gleichen Typ wie die Operanden. Der rechte Operand wird nur bedingt ausgewertet.

&&    bedingtes UND     p && q  wird zu  "wenn p dann q sonst false"
||    bedingtes ODER    p || q  wird zu  "wenn p dann true sonst q"
!     NICHT             !p      wird zu  "nicht p"

Addressoperatoren

Für einen Operanden x vom Typ T erzeugt die Adressieroperation &x einen Zeiger vom Typ *T auf x. Der Operand muss adressierbar sein, d.h. entweder eine Variable, eine Zeigerauflösung, eine Sliceindizierung, ein Feldselektor eines adressierbaren Struktur-Operanden oder eine Arrayindizierung auf ein adressierbares Array. Ausnahme zur geforderten Adressierbarkeit: x darf ein (möglicherweise geklammertes) Verbundliteral sein. Wenn die Auswertung von x zu einem Laufzeitfehler führt, dann auch die Auswertung von of &x.

Für einen Operanden x vom Zeigertyp *T bezeichnet *x die Variable vom Typ T, auf die x zeigt. Wenn x nil ist, dann führt *x zu einem Laufzeitfehler.

&x
&a[f(2)]
&Point{2, 3}
*p
*pf(x)

var x *int = nil
*x  // verursacht einen Laufzeitfehler
&*x // verursacht einen Laufzeitfehler

Empfangsoperator

Für einen Operanden ch eines Kanaltyps ist der Wert der Empfangsoperation <-ch der Wert, der über den Kanal ch empfangen wird. Die Kanalrichtung muss Empfangsoperationen zulassen, und der Typ der Empfangsoperation ist der Elementtyp des Kanals. Der Ausdruck blockiert, bis ein Wert zur Verfügung steht. Empfängt er von einem nil-Kanal, so blockiert er für immer. Eine Empfangsoperation auf einen geschlossenen Kanal wird sofort durchgeführt und dabei der Nullwert des Elementtyps zurückgegeben, nachdem zuvor alle vorher gesendeten Werte empfangen worden sind.

v1 := <-ch
v2 = <-ch
f(<-ch)
<-strobe  // warte auf Taktsignal & ignoriere den empfangenen Wert

Ein Empfangsausdruck in einer Zuweisung oder Initialisierung in der speziellen Form:

x, ok = <-ch
x, ok := <-ch
var x, ok = <-ch
var x, ok T = <-ch

liefert eine zusätzliches typfreies Boole'sches Ergebnis, welches anzeigt, ob die Kommunikation erfolgreich war. Der Wert von ok ist true, wenn der empfangene Wert durch eine erfolgreiche Sendeoperation in den Kanal geliefert wurde; er ist false, wenn ein Nullwert erzeugt wurde, weil der Kanal geschlossen und leer war.

Konversionen

Konversionen sind Ausdrücke der Form T(x), in denen T ein Typ ist und x ein Ausdruck, der nach T konvertiert werden kann.

Conversion = Type "(" Expression [ "," ] ")" .

Wenn der Typ mit einem Operator * oder <- oder dem Schlüsselwort func beginnt, dann muss er in Klammern gesetzt werden:

*Point(p)        // gleich *(Point(p))
(*Point)(p)      // p wird nach *Point konvertiert
<-chan int(c)    // gleich <-(chan int(c))
(<-chan int)(c)  // c wird nach <-chan int konvertiert
func()(x)        // Funktionssignatur func() x
(func())(x)      // x wird nach func() konvertiert
(func() int)(x)  // x wird nach func() int konvertiert
func() int(x)    // x wird nach func() int konvertiert (eindeutig)

Ein konstanter Wert x kann nach T konvertiert werden, wenn x durch einen Wert von T darstellbar ist. Als Sonderfall kann auch eine Ganzzahlkonstante x zu einem String-Typ konvertiert werden, indem dieselbe Regel wie für nicht-konstante x verwendet wird.

Die Konversion einer Konstanten ergibt eine typbehaftete Konstante.

uint(iota)                 // Jota-Wert vom Typ uint
float32(2.718281828)       // 2.718281828 vom Typ float32
complex128(1)              // 1.0 + 0.0i vom Typ complex128
float32(0.49999999)        // 0.5 vom Typ float32
float64(-1e-1000)          // 0.0 vom Typ float64
string('x')                // "x" vom Typ string
string(0x266c)             // "♬" vom Typ string
MeinString("foo" + "bar")  // "foobar" vom Typ MeinString
string([]byte{'a'})        // keine Konstante: []byte{'a'} ist keine Konstante
(*int)(nil)                // keine Konstante: nil ist keine Konstante, *int ist weder Boole'scher noch numerischer noch String-Typ
int(1.2)                   // verboten: 1.2 kann nicht als Ganzzahl repräsentiert werden
string(65.0)               // verboten: 65.0 ist keine Ganzzahlkonstante

Ein nicht-konstanter Wert x kann nach T in den folgenden Fällen konvertiert werden:

Damit Konversionen möglich bleiben, werden beim Vergleich von Strukturtypen die Markierungen ignoriert.

type Person struct {
    Name    string
    Address *struct {
        Street string
        City   string
    }
}

var data *struct {
    Name    string `json:"name"`
    Address *struct {
        Street string `json:"street"`
        City   string `json:"city"`
    } `json:"address"`
}

var person = (*Person)(data) // von Markierungen abgesehen sind die
                             // darunterliegenden Typen identisch.

Für die (nicht-konstanten) Konversionen zwischen numerischen Typen und von und nach String-Typen gibt es besondere Regeln. Solche Konversionen können die Repräsentation von x ändern und kosten Laufzeit. Alle anderen Konversionen ändern nur den Typ aber nicht die Repräsentation von x.

Es gibt kein Sprachkonstrukt, mit dem zwischen Zeigern und Ganzzahlen konvertiert werden könnte. Eine solche Funktionalität bietet in eingeschränktem Maße das Paket unsafe.

Konversion zwischen numerischen Typen

Für die Konversion nicht-konstanter Ganzzahlwerte gelten die folgenden Regeln:

  1. Ist bei Konversion zwischen Ganzzahltypen der Wert eine Ganzzahl mit Vorzeichen, so wird "vorzeichenerweitert", mit implizit unendlicher Genauigkeit. Im anderen Fall wird mit Null erweitert. Danach wird auf die Länge des Ergebnistyps abgeschnitten. Ist beispielsweise v := uint16(0x10F0), dann ist uint32(int8(v)) == 0xFFFFFFF0. Konversion ergibt immer einen gültigen Wert; es gibt keine Darstellung des Überlaufs.
  2. Wird eine Gleitkommazahl in einen Ganzzahltyp konvertiert, so wird der Bruchanteil verworfen (abgeschnitten).
  3. Wird eine Ganz- oder Gleitkommazahl in einen Gleitkommatyp konvertiert, oder eine Komplexzahl in einen anderen Komplextyp, so wird der Wert des Ergebnisses entsprechend dem Ergebnistyp gerundet. Beispielsweise kann der Wert einer Variablen x vom Typ float32 mit größerer Genauigkeit als eine "IEEE-754"-32-Bit-Zahl gespeichert sein, doch float32(x) repräsentiert den auf 32-Bit-Genauigkeit gerundeten Wert von x. Genauso kann x + 0.1 mehr als 32 Bit genau sein, aber float32(x + 0.1) nicht.

Alle nicht-konstante Konversionen, an denen Gleitkomma- oder Komplexwerte beteiligt sind, enden auch dann erfolgreich, wenn der Ergebnistyp das Ergebnis nicht fassen kann, aber der Ergebniswert ist abhängig von der Go-Implementierung.

Konversion von und nach String-Typen

  1. Die Konversion einer Ganzzahl mit oder ohne Vorzeichen in einen String-Typ ergibt einen String, der die UTF-8-Darstellung der Ganzzahl enthält. Werte, die keine gültigen Unicode-Kodenummern sind, werden nach "\uFFFD" konvertiert.
    string('a')         // "a"
    string(-1)          // "\ufffd" == "\xef\xbf\xbd"
    string(0xf8)        // "\u00f8" == "ø" == "\xc3\xb8"
    type MeinString string
    MeinString(0x65e5)  // "\u65e5" == "日" == "\xe6\x97\xa5"
    
  2. Die Konversion eines Byte-Slice in einen String-Typ ergibt einen String, dessen aufeinanderfolgende Bytes die Slice-Elemente sind.
    string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'})      // "hellø"
    string([]byte{})                                        // ""
    string([]byte(nil))                                     // ""
    
    type MeineBytes []byte
    string(MeineBytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'})  // "hellø"
    
  3. Die Konversion eines Rune-Slice in einen String-Typ ergibt einen String, der eine Verkettung der zu Strings konvertierten einzelnen Rune-Werten ist.
    string([]rune{0x767d, 0x9d6c, 0x7fd4})      // "\u767d\u9d6c\u7fd4" == "白鵬翔"
    string([]rune{})                            // ""
    string([]rune(nil))                         // ""
    
    type MeineRunen []rune
    string(MeineRunen{0x767d, 0x9d6c, 0x7fd4})  // "\u767d\u9d6c\u7fd4" == "白鵬翔"
    
  4. Die Konversion des Wertes eines String-Typs in ein Byte-Typ-Slice ergibt ein Slice, dessen aufeinanderfolgende Elemente die Bytes des Strings sind.
    []byte("hellø")      // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
    []byte("")           // []byte{}
    
    MeineBytes("hellø")  // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
    
  5. Die Konversion des Wertes eines String-Typs in ein Rune-Typ-Slice ergibt ein Slice, welches die einzelnen Unicode-Kodenummern des Strings enthält.
    []rune(MeinString("白鵬翔"))  // []rune{0x767d, 0x9d6c, 0x7fd4}
    []rune("")                   // []rune{}
    
    MeineRunen("白鵬翔")          // []rune{0x767d, 0x9d6c, 0x7fd4}
    

Konstante Ausdrücke

Konstante Ausdrücke dürfen nur konstante Operanden enthalten und werden zum Umwandlungszeitpunkt ausgewertet.

Typfreie Boole'sche, numerische oder String-Konstanten können überall dort als Operanden benutzt werden, wo Operanden von entsprechendem Boole'schem, numerischen oder String-Typ erlaubt sind.

Ein konstanter Vergleich ergibt immer eine typfreie Boole'sche Konstante. Ist der linke Operand eines konstanten Schiebe-Ausdrucks eine typfreie Konstante, so ist das Ergebnis eine Ganzzahlkonstante; im anderen Fall ist es eine Konstante desselben Typs wie der linke Operand, welcher von einem Ganzzahltyp sein muss.

Alle anderen Operationen auf typfreie Konstanten führen zu typfreien Konstanten der gleichen Art, das heißt zu einer Boole'schen, Ganzzahl-, Gleitkomma-, Komplex- oder String-Konstante. Sind die typfreien Operanden einer binären Operation (ohne die Schiebeoperationen) von verschiedener Art, so ist das Ergebnis von der Art des Operanden, der in der folgenden Liste später erscheint: Ganzzahl, Rune, Gleitkommazahl, Komplexzahl. Zum Beispiel ergibt eine typfreie Ganzzahl-Konstante dividiert durch eine typfreie Komplex-Konstante eine typfreie Komplex-Konstante.

const a = 2 + 3.0          // a == 5.0   (typfreie Gleitkommakonstante)
const b = 15 / 4           // b == 3     (typfreie Ganzzahlkonstante)
const c = 15 / 4.0         // c == 3.75  (typfreie Gleitkommakonstante)
const Θ float64 = 3/2      // Θ == 1.5   (Typ float64, 3/2 ist eine Ganzzahldivision)
const Π float64 = 3/2.     // Π == 1.5   (Typ float64, 3/2. ist eine Gleitkommadivision)
const d = 1 << 3.0         // d == 8     (typfreie Ganzzahlkonstante)
const e = 1.0 << 3         // e == 8     (typfreie Ganzzahlkonstante)
const f = int32(1) << 33   // verboten   (Konstante 8589934592 passt nicht in int32)
const g = float64(2) >> 1  // verboten   (float64(2) ist eine typbehaftete Gleitkommakonstante)
const h = "foo" > "bar"    // h == true  (typfreie Boole'sche Konstante)
const j = true             // j == true  (typfreie Boole'sche Konstante)
const k = 'w' + 1          // k == 'x'   (typfreie Runenkonstante)
const l = "hi"             // l == "hi"  (typfreie String-Konstante)
const m = string(k)        // m == "x"   (Typ string)
const Σ = 1 - 0.707i       //            (typfreie Komplexkonstante)
const Δ = Σ + 2.0e-4       //            (typfreie Komplexkonstante)
const Φ = iota*1i - 1/1i   //            (typfreie Komplexkonstante)

Anwendung der Standardfunktion complex auf eine typfreie Ganzzahl-, Runen- oder Gleitkommakonstante ergibt eine typfreie Komplexkonstante.

const ic = complex(0, c)  // ic == 3.75i (typfreie Komplexkonstante)
const iΘ = complex(0, Θ)  // iΘ == 1i    (Typ complex128)

Konstante Ausdrücke werden immer exakt ausgewertet; Zwischenergebnisse und die Konstanten selbst können deutlich größere Genauigkeit erfordern, als die in der Sprache vordeklarierten Typen unterstützen. Die folgenden Deklarationen sind erlaubt:

const Riesig = 1 << 100         // Riesig == 1267650600228229401496703205376  (typfreie Ganzzahlkonstante)
const Vier int8 = Riesig >> 98  // Vier == 4                                  (Typ int8)

Der Divisor in einer konstanten Divisions- oder Divisionsrestoperation darf nicht null sein:

3.14 / 0.0  // verboten: Division durch Null

Die Werte typbehafteter Konstanten müssen immer exakt als Werte des Konstantentyps darstellbar sein. Die folgenden konstanten Ausdrücke sind nicht erlaubt:

uint(-1)       // -1 nicht als uint darstellbar
int(3.14)      // 3.14 nicht als int darstellbar
int64(Riesig)  // 1267650600228229401496703205376 nicht als int64 darstellbar
Vier * 300     // Operand 300 nicht als int8 (Typ von Vier) darstellbar
Vier * 100     // Operand 400 nicht als int8 (Typ von Vier) darstellbar

Die Maske, die vom unären Bitweise-Komplement-Operator ^ benutzt wird, erfüllt die Bedingung für Nicht-Konstanten: die Maske besteht aus lauter Einsen für Konstanten ohne Vorzeichen und ist -1 für Konstanten mit Vorzeichen und für typfreie Konstanten.

^1         // typfreie Ganzzahlkonstante, gleich -2
uint8(^1)  // verboten: gleich uint8(-2), bereichsfremd
^uint8(1)  // Typbehaftete Konstante (uint8), gleich 0xFF ^ uint8(1) = uint8(0xFE)
int8(^1)   // gleich int8(-2)
^int8(1)   // gleich -1 ^ int8(1) = -2

Go-Implementierungseinschränkung: Ein Compiler darf bei der Berechnung typfreier Gleitkomma- oder Komplexausdrücke runden; siehe auch die Implementierungseinschränkung im Abschnitt Konstanten. Solches Runden kann dazu führen, dass ein Gleitkommaausdruck in einem Ganzzahlkontext ungültig wird, selbst wenn er bei unendlicher Genauigkeit gegen eine Ganzzahl tendiert, und umgekehrt. [?, A.d.Ü.]

Auswertungsreihenfolge

Auf Paketebene bestimmen Initialisierungsabhängigkeiten die Auswertungsreihenfolge der einzelnen Initialisierungsausdrücke in den Variablendeklarationen. Ansonsten werden beim Auswerten der Operanden eines Ausdrucks, einer Zuweisung oder einer Return-Anweisung alle Funktionsaufrufe, Methodenaufrufe und Kommunikationsoperationen lexikalisch von links nach rechts ausgewertet.

Zum Beispiel erfolgen in der (funktionslokalen) Zuweisung

y[f()], ok = g(h(), i()+x[j()], <-c), k()

die Funktionsaufrufe und die Kommunikation in der folgenden Reihenfolge: f(), h(), i(), j(), <-c, g() und k(). Dagegen ist die Reihenfolge dieser Ereignisse gegenüber der Auswertung und Indizierung von x oder gegenüber der Auswertung von y nicht festgelegt.

a := 1
f := func() int { a++; return a }
x := []int{a, f()}            // x kann [1, 2] oder [2, 2] sein:
                              // Auswertungsreihenfolge von a und f() ist nicht festgelegt
m := map[int]int{a: 1, a: 2}  // m kann {2: 1} oder {2: 2} sein:
                              // Auswertungsreihenfolge der beiden Map-Zuweisungen ist nicht festgelegt
n := map[int]int{a: f()}      // n kann {2: 3} oder {3: 3} sein:
                              // Auswertungsreihenfolge von Schlüssel und Wert ist nicht festgelegt

Auf Paketebene heben Initialisierungsabhängigkeiten die Von-links-nach-rechts-Regel für einzelne Initialisierungsausdrücke auf, aber nicht für Operanden innerhalb eines solchen Ausdrucks:

var a, b, c = f() + v(), g(), sqr(u()) + v()

func f() int        { return c }
func g() int        { return a }
func sqr(x int) int { return x*x }

// Funktionen u und v sind unabhängig von allen anderen Variablen und Funktionen

Die Funktionen werden in folgender Reihenfolge aufgerufen: u(), sqr(), v(), f(), v(), g().

Die Auswertung von Gleitkommaoperationen richtet sich nach der Assoziativität der Operatoren. Klammersetzung hebt die Standard-Assoziativität auf. Im Ausdruck x + (y + z) wird die Addition y + z vor dem Addieren von x durchgeführt.

Anweisungen

Anweisungen steuern die Ausführung.

Statement =
	Declaration | LabeledStmt | SimpleStmt |
	GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt |
	FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt |
	DeferStmt .

SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .

Terminierende Anweisungen

Eine terminierende Anweisung verhindert die Ausführung aller Anweisungen, die sich danach im selben Block befinden. Folgende Anweisungen sind terminierend:

  1. eine Return- oder eine Goto-Anweisung,
  2. ein Aufruf der Standardfunktion panic,
  3. ein Block, deren Anweisungsliste mit einer Terminierenden Anweisung endet,
  4. eine If-Anweisung, bei der:
    • der Else-Zweig vorhanden ist und
    • beide Zweige Terminierende Anweisungen sind,
  5. eine For-Anweisung, bei der:
    • keine Break-Anweisung sich auf die For-Anweisung bezieht und
    • die ohne Schleifenbedingung ist,
  6. eine Switch-Anweisung, bei der:
    • keine Break-Anweisung sich auf die Switch-Anweisung bezieht,
    • es einen Default-Fall gibt und
    • die Anweisungsliste jedes Falles, inklusive des Default-Falls, mit einer Terminierenden Anweisung oder einer Fallthrough-Anweisung, eventuell mit Sprungmarke, endet,
  7. eine Select-Anweisung bei der:
    • keine Break-Anweisung sich auf die Select-Anweisung bezieht und
    • die Anweisungsliste jedes Falles, inklusive des Default-Falls, falls es ihn gibt, mit einer Terminierenden Anweisung endet, oder
  8. eine Terminierende Anweisung mitSprungmarke.

Alle anderen Anweisungen sind nicht-terminierend

Eine Anweisungsliste endet mit einer Terminierenden Anweisung, wenn die Liste nicht leer ist und die letzte nicht leere Anweisung eine terminierende ist.

Leere Anweisungen

Die leere Anweisung tut nichts.

EmptyStmt = .

Markierte Anweisungen

Eine markierte Anweisung kann Ziel einer goto-, break- oder continue-Anweisung sein.

LabeledStmt = Label ":" Statement .
Label       = identifier .
Error: log.Panic("Fehler aufgetreten")

Ausdruck-Anweisungen

Mit Ausnahme bestimmter Standardfunktionen können Funktionsaufrufe und Methodenaufrufe und Empfangsoperationen im Kontext einer Anweisung erscheinen. Solche Anweisungen können in Klammern stehen.

ExpressionStmt = Expression .

Die folgenden Standardfunktionen sind in einem Anweisungskontext nicht erlaubt:

append cap complex imag len make new real
unsafe.Alignof unsafe.Offsetof unsafe.Sizeof
h(x+y)
f.Close()
<-ch
(<-ch)
len("foo")  // verboten, wenn len die Standardfunktion ist

Sendeanweisungen

Eine Sendeanweisung schickt einen Wert in einen Kanal. Der Kanalausdruck muss von einem Kanaltyp sein, die Kanalrichtung muss Sendeoperationen erlauben und der Typ des zu sendenden Werts muss dem Elementtyp des Kanals zuweisbar sein.

SendStmt = Channel "<-" Expression .
Channel  = Expression .

Sowohl der Kanal- als auch der Wertausdruck werden ausgewertet, bevor der Nachrichtenaustausch beginnt. Die Ausführung blockiert bis die Sendeoperation möglich ist. Eine Sendeoperation in einen ungepufferten Kanal ist möglich, wenn der Empfänger bereit ist. Eine Sendeoperation in einen gepufferten Kanal ist möglich, wenn noch Platz im Puffer ist. Eine Sendeoperation in einen geschlossenen Kanal ist möglich und führt zu einem Laufzeitfehler. Eine Sendeoperation in einen nil-Kanal blockiert für immer.

Kanäle sind Warteschlangen [englisch: first-in-first-out queues, A.d.Ü.]. Wenn also beispielsweise eine einzelne Goroutine Werte in einen Kanal sendet, aus dem von nur einer Goroutine empfangen wird, dann werden die Werte in derselben Reihenfolge empfangen wie sie gesendet wurden.

Ein einzelner Kanal kann von beliebig vielen Goroutinen für Sende- und Empfangsoperationen sowie für Aufrufe der Standardfunktionen cap und len benutzt werden.

ch <- 3

Inkrement/Dekrement-Anweisungen

Die Anweisungen "++" und "--" erhöhen oder erniedrigen ihren jeweiligen Operanden um die typfreie Konstante 1. Wie bei einer Zuweisung muss der Operand adressierbar oder der Indexausdruck einer Map sein.

IncDecStmt = Expression ( "++" | "--" ) .

Die folgenden Anweisungen/Zuweisungen bedeuten jeweils dasselbe:

InkDek-Anweisung  Zuweisung
x++               x += 1
x--               x -= 1

Zuweisungen

Assignment = ExpressionList assign_op ExpressionList .

assign_op = [ add_op | mul_op ] "=" .

Jeder linksseitige Operand muss adressierbar, der Indexausdruck einer Map oder, das aber nur in =-Zuweisungen, der Leere Bezeichner sein. Operanden dürfen in Klammern stehen.

x = 1
*p = f()
a[i] = 23
(k) = <-ch  // gleichwertig zu k = <-ch

Eine Zuweisung x op= y, wobei op ein binärer arithmetischer Operator ist, ist gleichbedeutend mit x = x op (y), wertet x aber nur einmal aus. Das Konstrukt op= ist eine Sinneinheit. In solchen Zuweisungen müssen sowohl die links- als auch die rechtsseitige Ausdrückeliste genau einen Ein-Wert-Ausdruck enthalten und der linksseitige Ausdruck darf nicht der Leere Bezeichner sein.

a[i] <<= 2
i &^= 1<<n

Eine Mehrfachzuweisung weist die einzelnen Elemente einer Operation mit Mehrfachergebnis einer Liste von Variablen zu. Es gibt zwei Formen. In der ersten Form ist der rechtsseitige Operand ein einzelner Mehrfachergebnis-Ausdruck, also ein Funktionsaufruf, eine Kanal- oder eine Map-Operation oder eine Typzusicherungen. Die Anzahl der Operanden auf der linken Seite muss gleich der Anzahl der Ergebniswerte sein. Wenn zum Beispiel f eine Funktion ist, die zwei Werte zurückgibt:

x, y = f()

, dann wird der erste Wert dem x und der zweite dem y zugewiesen. In der zweiten Form der Mehrfachzuweisung muss die Anzahl der Operanden links gleich der Anzahl der Ausdrücke rechts sein, wobei jeder Ausdruck genau einen Wert zurückgeben muss. Dann wird der Wert des n-ten Ausdrucks dem n-ten Operanden auf der linken Seite zugewiesen:

one, two, three = '一', '二', '三'

Der Leere Bezeichner ermöglicht, rechtsseitige Ergebniswerte eines Mehrfachergebnis-Ausdrucks zu ignorieren:

_ = x       // wertet x aus, aber ignoriert den Wert
x, _ = f()  // wertet f() aus, aber ignoriert den zweiten Ergebniswert

Die Zuweisung geschieht in zwei Phasen. Zunächst werden linksseitig die Operanden von Indexausdrücken und Zeigerauflösungen (inklusive der impliziten Zeigerauflösungen in Selektoren) sowie die rechtsseitigen Ausdrücke in der üblichen Reihenfolge ausgewertet. Dann werden die Zuweisungen von links nach rechts vorgenommen.

a, b = b, a              // tausche a und b

x := []int{1, 2, 3}
i := 0
i, x[i] = 1, 2           // setze i = 1 und x[0] = 2

i = 0
x[i], i = 2, 1           // setze x[0] = 2 und i = 1

x[0], x[0] = 1, 2        // setze x[0] = 1 und dann x[0] = 2 (so dass schließlich x[0] == 2 ist)

x[1], x[3] = 4, 5        // setze x[1] = 4 und dann: Panik beim Setzen von x[3] = 5.

type Point struct { x, y int }
var p *Point
x[2], p.x = 6, 7         // setze x[2] = 6 und dann: Panik beim Setzen p.x = 7

i = 2
x = []int{3, 5, 7}
for i, x[i] = range x {  // setze i, x[2] = 0, x[0]
	break
}
// nach dieser Schleife: i == 0 und x == []int{3, 5, 3}

Bei Zuweisungen muss jeder Wert dem Typ des Operanden, dem er zugewiesen wird, auch zuweisbar sein. Es gibt folgende Spezialfälle:

  1. Jeder typbehaftete Wert darf dem Leeren Bezeichner zugewiesen werden.
  2. Wird eine typfreie Konstante einer Variablen eines Interface-Typs oder dem Leeren Bezeichner zugewiesen, so wird die Konstante zunächst in ihren Standardtyp konvertiert.
  3. Wird ein typfreier Boole'scher Wert einer Variablen eines Interface-Typs oder dem Leeren Bezeichner zugewiesen, so wird er zunächst zum Typ bool konvertiert.

If-Anweisungen

If-Anweisungen legen die bedingte Ausführung zweier Verarbeitungszweige gemäß dem Wert eines Boole'schen Ausdrucks fest. Wenn der Ausdruck true ergibt, wird der If-Zweig ausgeführt, ansonsten, wenn vorhanden, der Else-Zweig.

IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
if x > max {
    x = max
}

Vor dem Boole'schen Ausdruck darf eine einfache Anweisung stehen, die ausgeführt wird, bevor der Ausdruck ausgewertet wird:

if x := f(); x < y {
    return x
} else if x > z {
    return z
} else {
    return y
}

Switch-Anweisungen

Switch-Anweisungen ermöglichen die Auswahl aus vielen Verarbeitungszweigen. Ein Ausdruck oder eine Typzusicherung wird mit Fallwerten verglichen, um zu entscheiden, welcher Zweig zu verarbeiten ist.

SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .

Es gibt zwei Formen: den Ausdruck-Switch und den Typ-Switch. Bei einem Ausdruck-Switch enthalten die Case-Klauseln Ausdrücke, die mit dem Wert des Switch-Ausdrucks verglichen werden. Bei einem Typ-Switch enthalten die Case-Klauseln Typen, die mit dem Typ des dafür besonders formulierten Switch-Ausdrucks verglichen werden. In einer Switch-Anweisung wird der Switch-Ausdruck genau einmal ausgewertet.

Ausdruck-Switch

Bei einem Ausdruck-Switch wird der Switch-Ausdruck ausgewertet, und die Case-Ausdrücke, die keine Konstanten zu sein brauchen, werden nacheinander von links nach rechts und von oben nach unten ausgewertet; die erste Übereinstimmung löst die Ausführung der Anweisungen aus, die zum entsprechenden Fall gehören; die restlichen Case-Klauseln werden übergangen. Gibt es keine Übereinstimmung, aber eine Default-Klausel, so werden deren Anweisungen ausgeführt. Es darf maximal eine Default-Klausel geben, die irgendwo in der Switch-Anweisung stehen darf. Ein fehlender Switch-Ausdruck ist gleichbedeutend mit dem Boole'schen Wert true.

ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" .
ExprCaseClause = ExprSwitchCase ":" StatementList .
ExprSwitchCase = "case" ExpressionList | "default" .

Wird der Switch-Ausdruck zu einer typfreien Konstanten ausgewertet, so wird er zuerst zu seinem Vorgabetyp konvertiert; wenn er ein typfreier Boole'scher Wert ist, wird er zuerst zum Typ bool konvertiert. Der vordeklarierte typfreie Wert nil kann nicht als Switch-Ausdruck benutzt werden.

Wenn der Case-Ausdruck typfrei ist, wird er zuerst zu dem Typ des Switch-Ausdrucks konvertiert. Für jeden Case-Ausdruck x und den Wert t des Switch-Ausdrucks muss x == t ein gültiger Vergleich sein.

Anders ausgedrückt wird ein Switch-Ausdruck so behandelt, als ob er zum Deklarieren und Initialisieren einer temporären Variablen t ohne expliziten Typ benutzt würde; gegen diesen Wert von t wird jeder Case-Ausdruck x auf Gleichheit geprüft.

Nur die jeweils letzte nicht-leere Anweisung einer Case- oder Default-Klausel darf eine Fallthrough-Anweisung (eventuell mit Sprungmarke) sein, und zeigt an, dass es danach mit der ersten Anweisung der nächsten Klausel weitergeht. Ohne Fallthrough geht's am Ende der Switch-Anweisung weiter. Eine Fallthrough-Anweisung darf letzte Anweisung jeder Klausel eines Ausdruck-Switch sein, außer der letzten.

Vor dem Switch-Ausdruck darf eine einfache Anweisung stehen, die ausgeführt wird, bevor der Ausdruck ausgewertet wird.

switch tag {
default: s3()
case 0, 1, 2, 3: s1()
case 4, 5, 6, 7: s2()
}

switch x := f(); {  // Switch-Ausdruck fehlt == "true"
case x < 0: return -x
default: return x
}

switch {
case x < y: f1()
case x < z: f2()
case x == 4: f3()
}

Go-Implementierungseinschränkung: Ein Compiler darf verbieten, dass mehrere Case-Ausdrücke zur gleichen Konstanten ausgewertet würden. Zum Beispiel verbieten die aktuellen Compiler doppelte Ganzzahl-, Gleitkomma- oder String-Konstanten in Case-Ausdrücken.

Typ-Switch

Ein Typ-Switch vergleicht Typen, nicht Werte. Davon abgesehen ist er dem Ausdruck-Switch ähnlich. Kennzeichnend ist ein besonderer Switch-Ausdruck in Form einer Typzusicherung, wobei statt eines echten Typs das Schlüsselwort type benutzt wird:

switch x.(type) {
// Case-Klauseln
}

Die Case-Klauseln vergleichen dann die jeweiligen Typen T mit dem dynamischen Typ des Ausdrucks x. Wie bei Typzusicherungen muss x von einem Interface-Typ sein und jeder Nicht-Interface-Typ T in einer Case-Klausel muss den Typ von x implementieren. Die in den Case-Klauseln aufgeführten Typen müssen sich alle voneinander unterscheiden.

TypeSwitchStmt  = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" .
TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" .
TypeCaseClause  = TypeSwitchCase ":" StatementList .
TypeSwitchCase  = "case" TypeList | "default" .
TypeList        = Type { "," Type } .

Die Wächterklausel (TypeSwitchGuard) kann eine Variablenkurzdeklaration enthalten. Wird diese Kurzform benutzt, so wird die Variable am Ende des TypeSwitchCase im impliziten Blocks jeder Klausel deklariert. Nennt die Case-Klausel genau einen Typ, so bekommt die Variable diesen Typ; andernfalls bekommt die Variable den Typ des Ausdrucks in der Wächterklausel.

Anstelle eines Typs kann die Case-Klausel den vordefinierten Bezeichner nil benutzen; sein; dieser Fall tritt ein, wenn der Ausdruck in der Wächterklausel ein nil-Interface-Wert ist. Es darf nur einen nil-Fall geben.

Für einen vorgegebenen Ausdruck x vom Typ interface{} könnte man den folgenden Typ-Switch:

switch i := x.(type) {
case nil:
    printString("x ist nil")                 // Typ von i ist Typ von x (interface{})
case int:
    printInt(i)                              // Typ von i ist int
case float64:
    printFloat64(i)                          // Typ von i ist float64
case func(int) float64:
    printFunction(i)                         // Typ von i ist func(int) float64
case bool, string:
    printString("Typ ist bool oder string")  // Typ von i ist Typ von x (interface{})
default:
    printString("unbekannter Typ")           // Typ von i ist Typ von x (interface{})
}

auch so schreiben:

v := x                                                  // x wird genau einmal ausgewertet
if v == nil {
    i := v                                              // Typ von i ist Typ von x (interface{})
    printString("x ist nil")
} else if i, isInt := v.(int); isInt {
    printInt(i)                                         // Typ von i ist int
} else if i, isFloat64 := v.(float64); isFloat64 {
    printFloat64(i)                                     // Typ von i ist float64
} else if i, isFunc := v.(func(int) float64); isFunc {
    printFunction(i)                                    // Typ von i ist func(int) float64
} else {
    _, isBool := v.(bool)
    _, isString := v.(string)
    if isBool || isString {
        i := v                                          // Typ von i ist Typ von x (interface{})
        printString("Typ ist bool oder string") 
    } else {
        i := v                                          // Typ von i ist Typ von x (interface{})
        printString("unbekannter Typ")
    }
}

Vor der Wächterklausel darf eine einfache Anweisung stehen, die ausgeführt wird, bevor die Klausel ausgewertet wird.

Die Anweisung fallthrough ist in einem Typ-Switch nicht erlaubt.

For-Anweisungen

Eine For-Anweisung legt die wiederholte Ausführung eines Blocks fest. Drei Formen davon gibt es: Diese Wiederholung (iteration) kann durch eine einzelne Bedingung, eine For-Klausel oder eine Range-Klausel kontrolliert werden.

ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .

For-Anweisungen mit einer einzelnen Bedingung

In ihrer einfachsten Form legt eine For-Anweisung die wiederholte Ausführung eines Blocks fest, solange ein Boole'scher Ausdruck true ist. Die Bedingung wird vor jeder Iteration ausgewertet. Fehlt die Bedingung, so ist das gleichbedeutend mit dem Boole'schen Wert true.

for a < b {
    a *= 2
}

For-Anweisungen mit einer for-Klausel

Eine For-Anweisung mit For-Klausel wird ebenfalls durch eine Bedingung kontrolliert, kann aber zusätzlich einen Startschritt (InitStmt) und einen Zählschritt (PostStmt) festlegen, z.B. in Form einer Zuweisung, oder einer Inkrement- oder Dekrement-Anweisung. Der Startschritt darf eine Variablenkurzdeklaration sein, der Zählschritt nicht. Variablen, die im Startschritt deklariert wurden, werden bei jeder Iteration wiederverwendet.

ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
InitStmt = SimpleStmt .
PostStmt = SimpleStmt .
for i := 0; i < 10; i++ {
    f(i)
}

Eine nicht-leerer Startschritt wird genau einmal ausgeführt, bevor die Bedingung für die erste Iteration ausgewertet wird, der Zählschritt nach jeder Ausführung des Blocks ausgeführt, und nur dann. Jedes Element der For-Klausel darf leer sein, die Semikolons jedoch sind zwingend, außer, wenn nur die Bedingung angegeben ist. Fehlt die Bedingung, so ist sie gleichbedeutend dem Boole'schen Wert true.

for cond { S() }    das gleiche wie:    for ; cond ; { S() }
for      { S() }    das gleiche wie:    for true     { S() }

For-Anweisungen mit einer range-Klausel

Eine For-Anweisung mit einer Range-Klausel iteriert über alle Elemente eines Arrays, eines Slices, eines Strings oder einer Map, oder den Werten, die über einen Kanal empfangen werden. Für jedes Element weist es die Iterations-Werte den zugehörigen Iterations-Variablen, soweit vorhanden, zu und führt dann den Block aus.

RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .

Der Ausdruck ganz rechts in der Range-Klausel heißt Range-Ausdruck, und er kann ein Array, ein Zeiger auf ein Array, ein Slice, ein String, eine Map oder ein Empfangsoperationen erlaubender Kanal sein. Wie bei einer Zuweisung müssen die Operanden auf der linken Seite, wenn vorhanden, adressierbar oder Map-Indexausdrücke sein; sie bezeichnen die Iterations-Variablen. Ist der Range-Ausdruck ein Kanal, so ist maximal eine Iterations-Variable erlaubt; sonst sind bis zu zwei. Wenn die letzte Iterations-Variable der Leere Bezeichner ist, dann ist diese Range-Klausel gleichbedeutend mit einer Range-Klausel ohne diesen Bezeichner.

Der Range-Ausdruck x wird genau einmal vor Ausführung der Schleife ausgewertet, mit einer Ausnahme: Wenn es höchstens eine Iterationsvariable gibt und len(x) konstant ist, dann wird der Range-Ausdruck gar nicht ausgewertet.

Funktionsaufrufe auf der linken Seite werden einmal pro Iteration ausgewertet. Bei jeder Iteration werden die Iterations-Werte wie folgt erzeugt (soweit die entsprechenden Iterationsvariablen vorhanden sind):

Range-Ausdruck                              1. Wert              2. Wert

Array oder Slice  a  [n]E, *[n]E, or []E    Index      i  int    a[i]          E
String            s  string type            Index      i  int    siehe unten   rune
Map               m  map[K]V                Schlüssel  k  K      m[k]          V
Kanal             c  chan E, <-chan E       Element    e  E
  1. Für einen Array-, Zeiger-auf-Array- oder Slice-Wert a werden die Indexwerte in aufsteigender Reihenfolge erzeugt, beginnend mit dem Elementindex 0. Wenn maximal eine Iterationsvariable angegeben ist, produziert die Range-Schleife Indexwerte von 0 bis len(a)-1, ohne auf das Array oder Slice zuzugreifen. Für ein nil-Slice ist die Anzahl der Iterationen 0.
  2. Für einen String-Wert iteriert die Range-Klausel über die im String enthaltenen Unicode-Kodenummern und beginnt beim Byte-Index 0. Bei den folgenden Iterationen ist der Indexwert dann jeweils der Index des ersten Bytes einer UTF-8-kodierten Kodenummer im String. Der zweite Wert vom Typ rune enthält dann den Wert der zugehörigen Kodenummer. Wenn die Iteration eine ungültige Kodenummer entdeckt, dann wird der zweite Wert zu 0xFFFD (dem Unicode-Ersatzzeichen), und die nächste Iteration schreitet im String um ein Byte voran.
  3. Die Iterationsreihenfolge über die Elemente von Maps ist weder festgelegt, noch wird garantiert, dass sie von einer Iteration zur nächsten dieselbe wäre. Wird ein Map-Element, das während einer Iteration noch nicht erreicht wurde, entfernt, dann wird der entsprechende Iterationswert nicht produziert. Wird während einer Iteration ein Element in der Map erzeugt, dann kann dieser Map-Eintrag von der Iteration erfasst oder auch übergangen werden. Das kann von einem erzeugten Wert zum nächsten und von einer Iteration zur nächsten anders sein. Für eine nil-Map ist die Anzahl der Iterationen 0.
  4. Für Kanäle werden als Iterationswerte die über den Kanal gesendeten einander folgenden Werte produziert; solange bis der Kanal geschlossen wird. Für einen nil-Kanal blockiert der Range-Ausdruck für immer.

Die Iterationswerte werden den entsprechenden Iterationsvariablen zugewiesen.

Die Iterationsvariablen dürfen in der Range-Klausel mithilfe einer Variablenkurzdeklaration (:=) deklariert werden. In diesem Fall wird ihr Typ der des jeweiligen Iterationswerts; ihre Reichweite ist der Block der For-Anweisung; innerhalb der Iteration werden sie wiederverwendet. Wurden die Variablen außerhalb der For-Anweisung deklariert, so werden nach deren Ausführung ihre Werte die aus der letzten Iteration sein.

var testdata *struct {
	a *[7]int
}
for i, _ := range testdata.a {
	// testdata.a wird nie ausgewertet; len(testdata.a) ist konstant
	// i reicht von 0 bis 6
	f(i)
}

var a [10]string
for i, s := range a {
	// Typ von i ist int
	// Typ von s ist string
	// s == a[i]
	g(i, s)
}

m := map[string]int{"Mo":1, "Di":2, "Mi":3, "Do":4, "Fr":5, "Sa":6, "So":7}
var key string
var val interface {}  // Elementtyp von m ist val zuweisbar
for key, val = range m {
	h(key, val)
}
// key == key aus der letzten Iteration
// val == m[key]

var ch chan Work = producer()
for w := range ch {
	doWork(w)
}

// Kanal leeren
for range ch {}

Go-Anweisungen

Eine Go-Anweisung startet die Ausführung eines Funktionsaufrufs in der Form eines unabhängigen Verarbeitungsstrangs [englisch: control thread, A.d.Ü.], genannt Goroutine, im selben Adressraum.

GoStmt = "go" Expression .

Der Ausdruck muss ein Funktions- oder Methodenaufruf sein; er darf nicht geklammert werden. Aufrufe von Standardfunktionen sind eingeschränkt wie bei Ausdruck-Anweisungen.

Der Funktionswert und die Parameter werden wie üblich von der rufenden Goroutine ausgewertet, aber anders als bei einem üblichen Aufruf wartet das Programm nicht auf das Ende der gerufenen Funktion. Stattdessen beginnt die Ausführung der Funktion unabhängig in einer neuen Goroutine. Wenn die Funktion endet, endet auch ihre Goroutine. Produziert die Funktion Ergebniswerte, so werden diese bei Funktionsende verworfen.

go Server()
go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)

Select-Anweisungen

Eine Select-Anweisung wählt aus einer Menge möglicher Kommunikationsoperationen eine zur Ausführung aus. Sie ähnelt einer Switch-Anweisung, nur dass alle Case-Klauseln sich auf Kommunikationsoperationen beziehen.

SelectStmt = "select" "{" { CommClause } "}" .
CommClause = CommCase ":" StatementList .
CommCase   = "case" ( SendStmt | RecvStmt ) | "default" .
RecvStmt   = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr .
RecvExpr   = Expression .

Der Empfangsausdruck (RecvExpr) muss eine Empfangsoperation sein. Für alle Fälle der Select-Anweisung werden die Kanalausdrücke von oben nach unten ausgewertet, zusammen mit allen Ausdrücken rechts von Sendeanweisungen. Ein Kanal darf nil sein; das ist dann so, als wäre dieser Fall gar nicht vorhanden, nur, dass bei einer Sendeanweisung dessen Ausdruck ausgewertet wird. Wenn Operationen entdeckt werden, die ausgeführt werden können, dann wird eine davon ausgewählt und die zugehörige Kommunikationsoperation und die zugehörigen Anweisungen werden ausgeführt; andernfalls, wenn es einen Default-Fall gibt, wird dieser ausgeführt; gibt es keinen Default-Fall, so blockiert die Select-Anweisung solange, bis eine der Kommunikationsoperationen ausgeführt werden kann. Es darf maximal eine Default-Klausel geben, und zwar an beliebiger Stelle in der Select-Anweisung. Gibt es keinen Fall mit Nicht-nil-Kanälen, so wartet die Anweisung endlos. Auch wenn die Anweisung warten muss, so werden die Kanal- und die Sendeausdrücke nur einmal, beim "Eintritt" in die Select-Anweisung, ausgewertet.

Dadurch dass alle Kanal- und alle Sendeausdrücke ausgewertet werden, treten Nebeneffekte, wenn, dann für alle Kommunikationsoperationen der Select-Anweisung auf.

Können mehrere Fälle ausgeführt werden, wird mit einem einheitlichen Pseudo-Zufallsverfahren eine der Kommunikationsoperation zur Ausführung ausgewählt.

Ein Fall mit Empfangsanweisung darf ein oder zwei neue Variablen mithilfe der Variablenkurzdeklaration deklarieren.

var c, c1, c2, c3 chan int
var i1, i2 int
select {
case i1 = <-c1:
    print(i1, " von c1 empfangen\n")
case c2 <- i2:
    print(i2, " nach c2 gesendet\n")
case i3, ok := (<-c3):  // gleich i3, ok := <-c3
    if ok {
        print(i3, " von c3 empfangen\n")
    } else {
        print("c3 ist geschlossen\n")
    }
default:
    print("kein Nachrichtenaustausch\n")
}

for {  // Sende eine zufällige Folge von "Bits" nach c
    select {
    case c <- 0:  // Beachte: Keine Anweisung, kein Fallthrough, keine Zusammenfassung von Cases
    case c <- 1:
    }
}

select {}  // Warte auf Godot !

Return-Anweisungen

Eine Return-Anweisung in einer Funktion F beendet die Ausführung von F und stellt optional dem Rufer ein oder mehrere Ergebniswerte zur Verfügung. Jede Funktion, die von F zurückgestellt wurde, wird ausgeführt, bevor F die Kontrolle an den Aufrufer zurückgibt.

ReturnStmt = "return" [ ExpressionList ] .

In einer Funktion ohne Ergebnistyp darf eine Return-Anweisung keine Ergebniswerte nennen.

func noResult() {
    return
}

Auf drei verschiedene Arten kann man für eine Funktion mit Ergebnistypen die Ergebniswerte zurückgeben:

  1. Die Ergebniswerte können explizit in der Return-Anweisung aufgezählt werden. Jeder Ausdruck darf nur einen Wert ergeben und muss dem entsprechenden Ergebnistyp der Funktion zuweisbar sein.
    func simpleF() int {
        return 2
    }
    
    func complexF1() (re float64, im float64) {
        return -7.0, -4.0
    }
    
  2. Die Liste der Ausdrücke in der Return-Anweisung kann ein Aufruf einer Funktion mit mehreren Ergebniswerten sein. Dies verhält sich so, als ob jeder Ergebniswert der Funktion einer temporären Variablen des entsprechenden Typs zugeordnet würde und im Anschluss daran eine Return-Anweisung mit einer Liste dieser Variablen stünde; ab hier greifen dann die Regeln des vorhergehenden Falls.
    func complexF2() (re float64, im float64) {
        return complexF1()
    }
    
  3. Die Liste der Ausdrücke darf leer sein, wenn der Ergebnistyp der Funktion Namen für die Ergebnisparameter angibt. Die Ergebnisparameter funktionieren wie normale lokale Variablen und die Funktion kann ihnen seiner Aufgabe entsprechend Werte zuweisen. Die Return-Anweisung gibt dann die Werte dieser Variablen zurück.
    func complexF3() (re float64, im float64) {
        re = 7.0
        im = 4.0
        return
    }
    
    func (devnull) Write(p []byte) (n int, _ error) {
        n = len(p)
        return
    }
    

Unabhängig davon, wie sie deklariert sind, werden alle Ergebniswerte beim Eintritt in die Funktion mit den Nullwerten ihres Typs initialisiert. Eine Return-Anweisung, die Ergebnisparameter angibt, setzt diese, bevor eventuelle zurückgestellte Funktionen ausgeführt werden.

Go-Implementierungseinschränkung: Ein Compiler kann eine leere Ausdrückeliste in einer Return-Anweisung verbieten, wenn es in Reichweite der Return-Anweisung ein anderes Objekt (Konstante, Typ oder Variable) mit demselben Namen wie dem eines Ergebnisparameters gibt.

func f(n int) (res int, err error) {
    if _, err := f(n-1); err != nil {
        return  // fehlerhafte Return-Anweisung: err ist abgeschattet
    }
    return
}

Break-Anweisungen

Eine Break-Anweisung [ohne Sprungmarke, A.d.Ü.] beendet die Ausführung der innersten For-, Switch- oder Select-Anweisung innerhalb derselben Funktion.

BreakStmt = "break" [ Label ] .

Ist eine Sprungmarke angegeben, so muss sie die einer übergeordneten For-, Switch- oder Select-Anweisung sein, und diese Anweisung wird dann beendet.

OuterLoop:
    for i = 0; i < n; i++ {
        for j = 0; j < m; j++ {
            switch a[i][j] {
            case nil:
                state = Error
                break OuterLoop
            case item:
                state = Found
                break OuterLoop
            }
        }
    }

Continue-Anweisungen

Eine Continue-Anweisung [ohne Sprungmarke, A.d.Ü.] beginnt die nächste Iteration der innersten For-Schleife mit deren Zählschritt. Die For-Schleife muss sich in derselben Funktion befinden.

ContinueStmt = "continue" [ Label ] .

Ist eine Sprungmarke angegeben, so muss sie die einer übergeordneten For-Anweisung sein, und diese Anweisung wird fortgeführt.

RowLoop:
    for y, row := range rows {
        for x, data := range row {
            if data == endOfRow {
                continue RowLoop
            }
            row[x] = data + bias(x, y)
        }
    }

Goto-Anweisungen

Eine Goto-Anweisung gibt die Kontrolle an die Anweisung mit der entsprechenden Sprungmarke innerhalb derselben Funktion weiter.

GotoStmt = "goto" Label .
goto Error

Die Ausführung der Goto-Anweisung darf nicht dazu führen, dass Variablen in Reichweite geraten, die sich nicht bereits beim Goto selbst in Reichweite befanden. Das folgende Beispiel:

    goto L  // SCHLECHT
    v := 3
L:

ist fehlerhaft, weil der Sprung zur Marke L die Erzeugung von v überspringt.

Eine Goto-Anweisung außerhalb eines Blocks kann nicht zu einer Marke in diesem Block springen. Das folgende Beispiel:

if n%2 == 1 {
    goto L1  // SCHLECHT
}
for n > 0 {
    f()
    n--
L1:
    f()
    n--
}

ist fehlerhaft, weil sich die Marke L1 innerhalb des For-Anweisungsblocks befindet, das Goto aber nicht.

Fallthrough-Anweisungen

Eine Fallthrough-Anweisung gibt die Kontrolle an die erste Anweisung der nächsten Case-Klausel einer Ausdruck-Switch-Anweisung weiter. Es darf nur als letzte nicht-leere Anweisung in einer solchen Klausel verwendet werden.

FallthroughStmt = "fallthrough" .

Defer-Anweisungen

Eine Defer-Anweisung ruft eine Funktion, deren Ausführung bis zu dem Zeitpunkt zurückgestellt wird, an dem die umschließende Funktion endet. Das, entweder weil die umschließende Funktion eine Return-Anweisung ausführte, oder weil das Ende ihres Funktionsrumpfs erreicht wurde, oder weil die betreffende Goroutine in Panik geriet.

DeferStmt = "defer" Expression .

Der Ausdruck muss der Aufruf einer Funktion oder Methode sein; er darf nicht geklammert sein. Aufrufe von Standardfunktionen sind eingeschränkt wie bei Ausdruck-Anweisungen.

Jedes Mal, wenn eine Defer-Anweisung ausgeführt wird, werden Wert und Parameter der zu rufenden Funktion wie üblich ausgewertet und gesichert aber die Funktion selbst wird nicht aufgerufen. Stattdessen werden zurückgestellte Funktionen in LIFO-Reihenfolge aufgerufen, direkt bevor die umschließende Funktion endet. Wenn der Wert einer zurückgestellten Funktion nil ist, gibt es einen Laufzeitfehler, wenn die Funktion aufgerufen wird, nicht schon bei der Ausführung von "defer".

Ist zum Beispiel die zurückgestellte Funktion ein Funktionsliteral und hat die umschließende Funktion namensbehaftete Ergebnisparameter, die ja im Funktionsliteral erreichbar sind, so kann die zurückgestellte Funktion auf diese Ergebnisparameter zugreifen und sie auch verändern, bevor sie zurückgegeben werden. Hat die zurückgestellte Funktion eigene Ergebniswerte, so werden diese bei Funktionsende verworfen. (Siehe auch den Abschnitt über die Handhabung von Laufzeitfehlern.)

lock(l)
defer unlock(l)  // Entsperren geschieht, bevor die umschließende Funktion endet

// druckt 3 2 1 0, bevor die umschließende Funktion endet
for i := 0; i <= 3; i++ {
    defer fmt.Print(i)
}

// f gibt 1 zurück
func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

Standardfunktionen

Standardfunktionen sind vordeklariert. Sie werden genau wie andere Funktionen gerufen; es gibt aber welche, die als ersten Parameter einen Typ statt eines Ausdrucks akzeptieren.

Weil Standardfunktionen keinen Standard-Go-Typ haben, dürfen sie nur in Aufrufen erscheinen; sie können also nicht als Funktionswerte benutzt werden.

Close

Für einen Kanal c legt close(c) fest, dass keine Werte mehr über den Kanal gesendet werden; es ist ein Fehler, wenn c nur Empfangskanal ist. Senden in einen Kanal oder Schließen eines bereits geschlossenen Kanals führt zu einem Laufzeitfehler. Das Schließen eines nil-Kanals führt ebenfalls zu einem Laufzeitfehler. Nach dem Aufruf von close und nachdem alle vorher gesendeten Werte empfangen worden sind, blockiert die Empfangsoperation nicht, sondern gibt den Nullwert des Elementtyps des Kanals zurück. Empfangsoperationen mit Mehrfach-Ergebniswerten geben den empfangenen Wert und einen Anzeiger dafür zurück, ob der Kanal geschlossen ist.

Länge und Kapazität

Die Standardfunktionen len und cap akzeptieren Argumente verschiedenen Typs und geben einen Wert vom Typ int zurück. Die Go-Implementierung garantiert, dass das Ergebnis immer in ein int hineinpasst.

Aufruf    Argumenttyp      Ergebnis

len(s)    String-Typ       String-Länge in Bytes
          [n]T, *[n]T      Array-Länge (== n)
          []T              Slice-Länge
          map[K]T          Map-Länge (Anzahl definierter Schlüssel)
          chan T           Anzahl von Elementen, die im Kanalpuffer warten

cap(s)    [n]T, *[n]T      Array-Länge (== n)
          []T              Slice-Kapazität
          chan T           Kanalpuffer-Kapazität

Die Kapazität eines Slice ist die Anzahl von Elementen, für die im zugrundeliegenden Array Platz vorhanden ist. Zu jedem Zeitpunkt gilt:

0 <= len(s) <= cap(s)

Die Länge von nil-Slices, -Maps oder -Kanälen ist 0. Die Kapazität von nil-Slices und -Kanälen ist 0.

Der Ausdruck len(s) ist konstant, wenn s eine String-Konstante ist. Die Ausdrücke len(s) und cap(s) sind konstant, wenn s vom Typ Array oder Zeiger auf ein Array ist und der Ausdruck s weder eine Empfangsoperation noch einen (nicht-konstante) Funktionsaufruf enthält; in solchen Fällen wird s nicht ausgewertet. In allen anderen Fällen sind Aufrufe von len und cap nicht konstant und s wird ausgewertet.

const (
    c1 = imag(2i)                    // imag(2i) = 2.0 ist eine Konstante
    c2 = len([10]float64{2})         // [10]float64{2} enthält keine Funktionsaufrufe
    c3 = len([10]float64{c1})        // [10]float64{c1} enthält keine Funktionsaufrufe
    c4 = len([10]float64{imag(2i)})  // imag(2i) ist eine Konstante und kein Funktionsaufruf
    c5 = len([10]float64{imag(z)})   // verboten: imag(z) ist ein (nicht-konstanter) Funktionsaufruf
)
var z complex128

Speicherzuteilung

Die Standardfunktion new akzeptiert einen Typ T, fordert zur Laufzeit Speicher für eine Variable dieses Typs an und gibt einen Wert vom Typ *T zurück, der darauf zeigt. Der Speicher wird so initialisiert, wie im Abschnitt über Nullwerte beschrieben.

new(T)

Zum Beispiel fordert

type S struct { a int; b float64 }
new(S)

Speicher für eine Variable vom Typ S, initialisiert ihn (a=0, b=0.0) und gibt einen Wert vom Typ *S zurück, der die Speicheradresse enthält.

Make für Slices, Maps und Kanäle

Die Standardfunktion make akzeptiert einen Typ T, der ein Slice-, Map- oder Kanaltyp sein muss; es folgt eine typspezifische Liste von Ausdrücken. Sie gibt einen Wert vom Typ T zurück (nicht *T). Speicher wird initialisiert, wie im Abschnitt über Nullwerte beschrieben.

Aufruf           Typ T      Ergebnis

make(T, n)       Slice      Slice vom Typ T mit Länge n und Kapazität n
make(T, n, m)    Slice      Slice vom Typ T mit Länge n und Kapazität m

make(T)          Map        Map vom Typ T
make(T, n)       Map        Map vom Typ T anfänglich mit Platz für etwa n Elemente

make(T)          Kanal      ungepufferter Kanal vom Typ T
make(T, n)       Kanal      gepufferter Kanal vom Typ T mit Puffergröße n

Beide Größenargumente n und m müssen von einem Ganzzahltyp oder typfreie Konstanten sein. Ein konstantes Größenargument darf nicht negativ und muss als Wert vom Typ int darstellbar sein; ist es eine typfreie Konstante, so bekommt es den Typ int. Wenn sowohl n als auch m angegeben und konstant sind, dann darf n nicht größer als m sein. Ist zur Laufzeit n negativ oder größer als m, so tritt ein Laufzeitfehler auf.

s := make([]int, 10, 100)       // Slice mit len(s) == 10, cap(s) == 100
s := make([]int, 1e3)           // Slice mit len(s) == cap(s) == 1000
s := make([]int, 10, 0)         // verboten: len(s) > cap(s)
s := make([]int, 1<<63)         // verboten: len(s) ist nicht als Wert vom Typ int darstellbar
c := make(chan int, 10)         // Kanal mit Puffergröße 10
m := make(map[string]int, 100)  // Map mit anfänglich Platz für etwa 100 Elemente

Der Aufruf von make mit einem Map-Typ und einem Größenhinweis n erzeugt eine Map mit einer Anfangsgröße für n Map-Elemente. Das genaue Verhalten ist von der Implementierung abhängig.

Append und Copy für Slices

Die Standardfunktionen append und copy helfen bei gängigen Slice-Operationen. Bei beiden Funktionen ist das Ergebnis unabhängig davon, ob der Speicherplatz der mitgegebenen Argumente überlappt.

Die variadische Funktion append fügt einen oder mehrere Werte x an ein Slice s eines Slice-Typs S an, und gibt ein Slice, das ebenfalls vom Typ S ist, zurück. Die Werte x werden einem Parameter vom Typ ...T übergeben, wobei T der Elementtyp von S ist und die entsprechenden Parameterübergaberegeln angewendet werden. Als Spezialfall akzeptiert append auch ein erstes Argument, das einem Typ []byte zuweisbar ist zusammen mit einem zweiten Argument mit einem String-Typ gefolgt von ... . Diese Form fügt die Bytes des Strings an.

append(s S, x ...T) S  // T ist der Elementtyp von S

Wenn die Kapazität von s nicht ausreicht, die zusätzlichen Werte aufzunehmen, dann erzeugt append ein neues, genügend großes zugrundeliegendes Array, das sowohl die existierenden Slice-Elemente als auch die zusätzlichen Werte aufnehmen kann. Ansonsten verwendet append das bereits existierende zugrundeliegende Array.

s0 := []int{0, 0}
s1 := append(s0, 2)        // ein Element anhängen        s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)  // mehrere Elemente anhängen   s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)    // ein Slice anhängen          s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...)  // überlappendes Slice  s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

var t []interface{}
t = append(t, 42, 3.1415, "foo")  //                      t == []interface{}{42, 3.1415, "foo"}

var b []byte
b = append(b, "bar"...)  // Inhalt eines Strings anhängen b == []byte{'b', 'a', 'r' }
s4 := append(s3[3:6], s3[2:]...) // append overlapping slice  s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

var t []interface{}
t = append(t, 42, 3.1415, "foo")   //                             t == []interface{}{42, 3.1415, "foo"}

var b []byte
b = append(b, "bar"...)            // append string contents      b == []byte{'b', 'a', 'r' }

Die Funktion copy kopiert Slice-Elemente von einer Quelle src nach einem Ziel dst und gibt die Anzahl der kopierten Elemente zurück. Beide Argumente müssen vom gleichen Elementtyp T und einem Slice vom Typ []T zuweisbar sein. Anzahl der kopierten Elemente ist die kleinere der beiden Längen len(src) und len(dst). Als Spezialfall akzeptiert copy ein Zielargument, das dem Typ []byte zuweisbar ist, zusammen mit einem Quellargument mit einem String-Typ. Diese Form kopiert Bytes aus dem String in ein Byte-Slice.

copy(dst, src []T) int
copy(dst []byte, src string) int

Beispiele:

var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
var b = make([]byte, 5)
n1 := copy(s, a[0:])           // n1 == 6, s == []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:])           // n2 == 4, s == []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "Hallo, Welt!")  // n3 == 5, b == []byte("Hallo")

Delete für Map-Elemente

Die Standardfunktion delete entfernt das Element mit dem Schlüssel k aus einer Map m. Der Typ von k muss dem Schlüsseltyp von m zuweisbar sein.

delete(m, k)  // Entferne Element m[k] aus der Map m

Wenn die Map m nil ist oder das Element m[k] nicht existiert, dann tut delete nichts.

Manipulieren von komplexen Zahlen

Drei Funktionen erzeugen und zerlegen komplexe Zahlen. Die Standardfunktion complex konstruiert einen Komplexwert aus einem Gleitkommateil für den Real- und einem für den Imaginäranteil. Die Funktionen real und imag extrahieren den Real- bzw. den Imaginärteil aus einem Komplexwert.

complex(realPart, imaginaryPart floatT) complexT
real(complexT) floatT
imag(complexT) floatT

Die Typen der Argumente und der Ergebniswerte entsprechen sich. Im Fall von complex müssen die beiden Argumente vom selben Gleitkommatyp sein und der Ergebnistyp ist der Komplextyp, dessen Bestandteile vom selben Gleitkommatyp sind, nämlich: complex64 für float32-Argumente und complex128 für float64-Argumente. Wird eines der Argumente zu einer typfreien Konstanten ausgewertet, so wird es zuerst zum Typ des anderen Arguments konvertiert. Werden beide Argumente zu typfreien Konstanten ausgewertet, so dürfen sie keine Komplexzahlen sein oder ihr imaginärer Teil muss Null sein; der Rückgabewert der Funktion ist eine typfreie komplexe Konstante.

In den Fällen real und imag muss das Argument vom Komplextyp sein, und der Ergebniswert ist vom korrespondierenden Gleitkommatyp, nämlich: float32 für ein complex64-Argument und float64 für ein complex128-Argument. Wird das Argument zu einer typfreien Konstanten ausgewertet, so muss es eine Zahl sein; der Rückgabewert der Funktion ist eine typfreie Gleitkommakonstante.

Die Funktionen real und imag bilden zusammen das Inverse von complex, so dass für einen Wert z vom Komplextyp Z gilt: z == Z(complex(real(z), imag(z))).

Sind die Operanden dieser Funktionen Konstanten, so ist auch der Ergebniswert eine Konstante.

var a = complex(2, -2)             // complex128
const b = complex(1.0, -1.4)       // typfreie Komplexkonstante 1 - 1.4i
x := float32(math.Cos(math.Pi/2))  // float32
var c64 = complex(5, -x)           // complex64
var s uint = complex(1, 0)         // typfreie Komplexkonstante 1 + 0i kann nach uint konvertiert werden
_ = complex(1, 2<<s)               // verboten: 2 gilt als Gleitkommatyp; kann nicht verschoben werden
var rl = real(c64)                 // float32
var im = imag(b)                   // float64
const c = imag(b)                  // typfreie Konstante -1.4
_ = imag(3 << s)                   // verboten: 3 gilt als Komplextyp; kann nicht verschoben werden

Handhabung von Laufzeitfehlern

Die zwei Standardfunktionen panic und recover unterstützen beim Melden und Behandeln von Laufzeitfehlern ebenso wie dem von programmspezifischen Fehlersituationen.

func panic(interface{})
func recover() interface{}

Ein expliziter Aufruf von panic oder ein Laufzeitfehler während der Ausführung von F stoppt die Ausführung von F. Alle Funktionen, deren Ausführung durch F zurückgestellt wurden, werden wie üblich abgearbeitet. Danach werden alle zurückgestellten Funktionen des Aufrufers von F abgearbeitet, und so weiter bis zu allen zurückgestellten Funktionen der Top-Level-Funktion der betroffenen Goroutine. Hier nun endet das Programm, und der Fehlerzustand, inklusive dem Aufrufargument von panic, wird gemeldet. Diese Abwicklungssequenz nennen wir Panik.

panic(42)
panic("unerreichbar")
panic(Error("parsen nicht möglich"))

Die Funktion recover erlaubt einem Programm, das Verhalten einer panischen Goroutine zu steuern. Nehmen wir den Fall einer Funktion G, die eine Funktion D zurückstellt, die wiederum recover aufruft, und nehmen wir weiter an, dass in einer Funktion, die in derselben Goroutine arbeitet wie G eine Panik auftritt. Wenn nun die Abarbeitung der zurückgestellten Funktionen bei D ankommt, wird der Ergebniswert von Ds Aufruf von recover der sein, der zuvor dem Aufruf von panic übergeben wurde. Wenn D nun normal endet ohne erneut panic auszulösen, dann stoppt die Panik-Abwicklungssequenz. Dann wird auch der Status der Funktionen zwischen G und dem Aufruf von panic zurückgesetzt, und normal weitergearbeitet. Alle Funktionen, die durch G vor D zurückgestellt wurden, werden dann abgearbeitet, die Verarbeitung von G endet mit der Rückkehr zu seinem Rufer.

Der Ergebniswert von recover ist nil in folgenden Fällen:

Die Funktion protect im folgenden Beispiel ruft das Argument g (eine Funktion) und schützt Aufrufer vor Laufzeitfehlern von g:

func protect(g func()) {
    defer func() {
        log.Println("Fertig")  // Println arbeitet normal, auch bei Panik
        if x := recover(); x != nil {
            log.Printf("Panik zur Laufzeit: %v", x)
        }
    }()
    log.Println("Start")
    g()
}

Bootstrapping

Die derzeitigen Go-Implementierungen kennen Standardfunktionen, die bei der Generierung der Go-Umgebung hilfreich sind. Sie werden hier der Vollständigkeit halber dokumentiert; es wird jedoch nicht garantiert, dass sie Teil der Sprache bleiben. Sie haben keine Ergebniswerte.

Funktion   Verhalten

print      druckt alle Argumente; Formatierungen sind abhängig von der Go-Implementierung
println    wie print, nur mit Leerzeichen zwischen Argumenten und Zeilenvorschub am Ende

Go-Implementierungseinschränkung: print und println brauchen nicht beliebige Argumenttypen zu akzeptieren; aber Boole'sche, numerische und String-Typen müssen unterstützt werden.

Pakete

Go-Programme werden konstruiert, indem Pakete zusammengebunden werden. Ein Paket wiederum wird aus einer oder mehr Quelldateien konstruiert, die die Konstanten, Typen, Variablen und Funktionen deklarieren, die zum Paket gehören und die von allen Dateien desselben Pakets aus erreichbar sind. Solche Bestandteile können auch exportiert und in anderen Paketen benutzt werden.

Aufbau der Quelldateien

Jede Quelldatei enthält eine Paket-Klausel, die festlegt zu welchem Paket sie gehört. Es folgt eine, möglicherweise leere, Menge aus Importdeklarationen, die aussagt, von wo Inhalte genutzt werden sollen. Darauf folgt eine, möglicherweise leere, Menge von Funktions-, Typen-, Variablen- und Konstantendeklarationen.

SourceFile       = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .

Paket-Klausel

Eine Paket-Klausel steht am Anfang jeder Quelldatei und definiert das Paket, zu dem die Datei gehört.

PackageClause  = "package" PackageName .
PackageName    = identifier .

Der Paketname darf nicht der Leere Bezeichner sein. Beispiel:

package math

Die Menge der Dateien mit demselben Paketnamen bilden die Implementierung des Pakets. Eine Go-Implementierung kann verlangen, dass alle Quelldateien eines Paktes sich im selben Verzeichnis befinden.

Importdeklarationen

Eine Importdeklaration zeigt an, dass die sie enthaltende Quelldatei von Funktionalität des importierten Pakets abhängt (siehe: Abschnitt "Initialisierung und Ausführung"), und ermöglicht den Zugriff auf exportierte Bezeichner dieses Pakets. Der Import nennt einen Bezeichner (Paketname) für die Zugriffe und einen Import-Pfad, der festlegt, welches Paket importiert wird.

ImportDecl       = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
ImportSpec       = [ "." | PackageName ] ImportPath .
ImportPath       = string_lit .

Der Paketname wird in qualifizierten Bezeichnern benutzt, um exportierte Bezeichner eines Pakets innerhalb der importierenden Quelldatei anzusprechen. Er wird im Dateiblock deklariert. Wird der Paketname weggelassen, so gilt der Bezeichner aus der Paket-Klausel des importierten Pakets. Erscheint explizit ein Punkt (.) anstelle des Namens, so werden alle exportierten Bezeichner, die im Paketblock des importierten Pakets deklariert sind, im Dateiblock der importierenden Quelldatei deklariert und müssen ohne Qualifizierung angesprochen werden.

Wie der Import-Pfad interpretiert wird, hängt von der Go-Implementierung ab; er ist aber typischerweise ein Teil des vollständigen Dateinamens des kompilierten Pakets; er kann ein Pfad relativ zu einem Repositorium installierter Pakete sein.

Go-Implementierungseinschränkung: Ein Compiler kann Import-Pfade weiter einschränken auf nicht-leere Zeichenketten, die nur aus Zeichen der allgemeinen Unicode-Kategorien L, M, N, P und S bestehen (grafische Zeichen ohne Leerzeichen), und darf auch die Zeichen !"#$%&'()*,:;<=>?[\]^`{|} sowie das Unicode-Ersatzzeichen U+FFFD ausschließen.

Nehmen wir an, wir haben ein Paket mit der Paketklausel package math kompiliert, die eine Funktion Sin exportiert, und nehmen wir weiter an, dass wir dieses Paket in eine Datei, die durch "lib/math" identifiziert wird, installiert haben. Dann zeigt die folgende Tabelle, wie man Sin nach den verschiedenen Arten der Importdeklaration anspricht:

Importdeklaration           Lokaler Name von Sin

import   "lib/math"         math.Sin
import m "lib/math"         m.Sin
import . "lib/math"         Sin

Eine Importdeklaration deklariert die Abhängigkeitsbeziehung zwischen dem importierenden und dem importierten Paket. Es ist nicht erlaubt, dass ein Paket sich selbst direkt oder indirekt importiert, und es ist nicht erlaubt, ein Paket zu importieren, ohne einen der exportierten Bezeichner anzusprechen. Um ein Paket nur wegen eines Nebeneffekts (Initialisierung) zu importieren, benutzen sie den Leeren Bezeichner als Paketnamen:

import _ "lib/math"

Ein Beispielpaket

Hier ist ein komplettes Go-Paket, das ein nebenläufiges Primzahlensieb implementiert:

package main

import "fmt"

// Sende die Sequenz 2, 3, 4, … in den Kanal 'ch'.
func generate(ch chan<- int) {
    for i := 2; ; i++ {
        ch <- i  // Sende 'i' in den Kanal 'ch'.
    }
}

// Kopiere die Werte aus dem Kanal 'src' in den Kanal 'dst',
// sortiere aber solche aus, die durch 'prime' teilbar sind.
func filter(src <-chan int, dst chan<- int, prime int) {
    for i := range src {  // Für alle von 'src' empfangenen Werte.
        if i%prime != 0 {
            dst <- i  // Sende 'i' in den Kanal 'dst'.
        }
    }
}

// Das Primzahlensieb: Verkette die Filter-Prozesse.
func sieve() {
    ch := make(chan int)  // Erzeuge einen neuen Kanal.
    go generate(ch)       // Starte generate() als Unterprozess.
    for {
        prime := <-ch
        fmt.Print(prime, "\n")
        ch1 := make(chan int)
        go filter(ch, ch1, prime)
        ch = ch1
    }
}

func main() {
    sieve()
}

Initialisierung und Ausführung

Nullwerte

Immer wenn für eine Variable Speicherplatz zugeteilt wird, sei es durch eine Deklaration, sei es durch einen Aufruf von new, oder wenn ein neuer Wert erzeugt wird, sei es mittels Verbundliteral, sei es durch Aufruf von make, und ohne dass für eine explizite Vorbelegung gesorgt wird, dann erhält die Variable oder der Wert einen Standardwert. Jeder Bestandteil einer solchen Variablen oder eines solchen Werts wird auf den Nullwert seines jeweiligen Typs gesetzt: false für Boole'sche Werte, 0 für Zahlen, "" für Strings und nil für Zeiger, Funktionen, Interfaces, Slices, Kanäle und Maps. Diese Vorbelegung erfolgt rekursiv, so dass zum Beispiel jedes einzelne Element eines Arrays aus Strukturen nullisiert wird, wenn kein Wert angegeben ist.

Die folgenden zwei einfachen Deklarationen sind gleichwertig:

var i int
var i int = 0

Und nach:

type T struct { i int; f float64; next *T }
t := new(T)

gilt:

t.i == 0
t.f == 0.0
t.next == nil

Dasselbe würde gelten nach:

var t T

Paketinitialisierung

Innerhalb eines Pakets werden Variablen auf Paketebene in der Reihenfolge ihrer Deklarationen initialisiert, aber nach allen Variablen, von denen sie abhängen.

Genauer gesagt gilt eine Variable auf Paketebene als zum Initialisieren bereit, wenn sie noch nicht initialisiert wurde, und entweder ohne Initialisierungsausdruck ist oder dieser nicht mehr von nicht-initialisierten Variablen abhängt. Schrittweise wird solange die jeweils nächste Variable in der Reihenfolge auf Paketebene initialisiert, die zum Initialisieren bereit ist, bis es keine solche mehr gibt.

Wenn dieser Prozess endet und es gibt noch immer nicht-initialisierte Variablen, so sind diese Variablen Teil einer oder mehrerer Initialisierungsschleifen, und damit ist das Programm ungültig.

Die Deklarationsreihenfolge von Variablen aus verschiedenen Quelldateien wird durch die Reihenfolge festgelegt, in der diese Dateien dem Compiler präsentiert werden: Variablen, die in der ersten Datei deklariert sind, werden auch vor allen Variablen deklariert, die in der zweiten Datei deklariert sind, und so weiter.

Die Analyse der Abhängigkeiten baut nicht auf die tatsächlichen Werte der Variablen auf, sondern auf lexikalischen Verweise auf sie, die transitiv im Quelltext gesucht werden. Wenn zum Beispiel der Initialisierungsausdruck einer Variablen x auf eine Funktion verweist, deren Funktionsrumpf wiederum auf eine Variable y verweist, dann hängt x von y ab. Im einzelnen:

Die Analyse der Abhängigkeiten wird pro Paket durchgeführt; es werden nur Verweise auf Variablen, Funktionen und Methoden berücksichtigt, die im betrachteten Paket deklariert sind.

Für die folgenden Deklarationen zum Beispiel:

var (
    a = c + b
    b = f()
    c = f()
    d = 3
)

func f() int {
    d++
    return d
}

ist die Initalisierungsreihenfolge: d, b, c, a.

Variablen können auch vorbelegt werden mithilfe von Funktionen namens init ohne Argumente und Ergebnisparameter, die im Paketblock deklariert sind.

func init() { … }

In einem Paket dürfen mehrere solcher Funktionen definiert werden, sogar in derselben Quelldatei. Im Paketblock darf der Bezeichner init nur benutzt werden, um init-Funktionen zu deklarieren, aber der Bezeichner selbst ist nicht deklariert, also kann auf init-Funktionen nicht verwiesen werden.

Ein Paket ohne Importdeklaration wird initialisiert, indem allen Variablen auf Paketebene Initialwerte zugewiesen werden und danach alle init-Funktionen in der Reihenfolge ihres Erscheinens im Quelltext aufgerufen werden, möglicherweise abhängig davon, in welcher Reihenfolge Quelltext aus mehreren Dateien dem Compiler präsentiert wird. Enthält ein Paket Importdeklarationen, so werden die importierten Pakete vor dem betrachteten Paket initialisiert. Wenn mehrere Pakete dasselbe Paket importieren, dann wird das importierte Paket nur einmal initialisiert. Durch seine Konstruktion garantiert der Importvorgang, dass keine zyklischen Initialisierungsabhängigkeiten auftreten können.

Paketinitialisierung — also die Variableninitialisierungen und die Aufrufe der init-Funktionen — geschieht in einer einzigen Goroutine sequentiell, für ein Paket nach dem anderen. Eine init-Funktion darf weitere Goroutinen starten, die nebenläufig zum Initialisierungskode laufen können. Wie auch immer, die Initialisierung reiht die init-Funktionen hintereinander: keine init-Funktion wird aktiviert, bevor nicht die vorhergehende geendet hat.

Um für das Initialisieren ein reproduzierbares Verhalten zu garantieren, wird für Umwandlungswerkzeuge empfohlen, dem Compiler die Dateien eines Pakets in lexikalischer Reihenfolge der Programmnamen zu präsentieren.

Programmausführung

Ein vollständiges Programm wird erzeugt, indem ein einziges, nicht importiertes Paket, genannt main-Paket, mit allen von ihm — auch transitiv — importierten Paketen gebunden wird. Das main-Paket muss den Paketnamen main tragen und eine Funktion main ohne Argumente und ohne Ergebnisparameter deklarieren.

func main() { … }

Die Programmausführung beginnt mit dem Initialisieren des main-Pakets, gefolgt vom Aufruf der Funktion main. Wenn diese Funktion endet, endet auch das Programm; es wartet nicht auf das Ende anderer Nicht-main-Goroutinen.

Fehler

Der vordeklarierte Typ error ist so definiert:

type error interface {
    Error() string
}

Dies ist das übliche Interface, um Fehler darzustellen; der nil-Wert steht für "kein Fehler". Zum Beispiel kann eine Funktion zum Lesen aus einer Datei so definiert sein:

func Read(f *File, b []byte) (n int, err error)

Laufzeitfehler

Fehler während der Programmausführung, wie zum Beispiel der Versuch, mit einem Index über ein Array hinauszugreifen, veranlassen einen Laufzeitfehler, äquivalent einem Aufruf der Standardfunktion panic mit einem Wert des von der Go-Implementierung definierten Interface-Typs runtime.Error. Dieser Typ genügt dem vordeklarierten Interface-Typ error. Die genauen Fehlerwerte, die für unterschiedliche Laufzeitfehler stehen, sind nicht festgelegt.

package runtime

type Error interface {
    error
    // und eventuell andere Methoden
}

Systemüberlegungen

Das Paket "unsafe"

Das Standardpaket unsafe, welches dem Compiler bekannt ist und das über den Importpfad erreichbar ist, bietet Hilfsmittel für "Low-Level"-Programmierung, wozu auch Operationen gehören, die das Typsystem verletzen. Ein Paket, welches unsafe benutzt, muss gründlichst auf Typsicherheit geprüft werden, und ist möglicherweise nicht portierbar. Unsafe bietet die folgende Schnittstelle:

package unsafe

type ArbitraryType int  // Abkürzung für einen beliebigen Go-Typ; kein wirklicher Typ
type Pointer *ArbitraryType

func Alignof(variable ArbitraryType) uintptr
func Offsetof(selector ArbitraryType) uintptr
func Sizeof(variable ArbitraryType) uintptr

Ein Zeiger ist ein Zeigertyp, jedoch der Zeigerwert muss nicht aufgelöst werden. Jeder Zeiger oder Wert vom Ursprungstyp uintptr kann zu einem Typ mit Ursprungstyp Zeiger konvertiert werden, und umgekehrt. Auswirkungen der Konversion zwischen Zeiger und uintptr sind implementierungsabhängig.

var f float64
bits = *(*uint64)(unsafe.Pointer(&f))

type ptr unsafe.Pointer
bits = *(*uint64)(ptr(&f))

var p ptr = nil

Die Funktionen Alignof und Sizeof erwarten einen Ausdruck x beliebigen Typs, und geben entsprechend die Ausrichtung oder die Größe einer hypothetischen Variablen v zurück, als wäre v durch var v = x deklariert worden.

Die Funktion Offsetof erwartet einen (möglicherweise geklammerten) Selektor s.f, welcher für ein Feld f steht aus einer Struktur, die mit s oder *s bezeichnet wird, und gibt den Versatz relativ zur Strukturadresse in Byte zurück. Ist f ein eingebettetes Feld, so muss es ohne Zeigerauflösung direkt über Felder der Struktur erreichbar sein. Für eine Struktur s mit einem Feld f gilt:

uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))

Eine Computerarchitektur kann verlangen, dass Speicheradressen ausgerichtet sein müssen, d.h. die Adresse einer Variablen muss das Vielfache eines Faktors sein; das ist die Ausrichtung des Typs der Variablen. Die Funktion Alignof erwartet einen Ausdruck, der für eine Variable beliebigen Typs steht, und gibt die Ausrichtung des Variablentyps in Byte zurück. Für eine Variable x gilt:

uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0

Aufrufe von Alignof, Offsetof und Sizeof sind konstante Ausdrücke vom Typ uintptr zum Umwandlungszeitpunkt.

Größen- und Ausrichtungsgarantien

Für numerische Typen werden folgende Größen garantiert:

Typ                                 Größe in Byte

byte, uint8, int8                     1
uint16, int16                         2
uint32, int32, float32                4
uint64, int64, float64, complex64     8
complex128                           16

Die folgenden Ausrichtungseigenschaften werden mindestens garantiert:

  1. Für eine Variable x beliebigen Typs gilt: unsafe.Alignof(x) ist mindestens 1.
  2. Für eine Variable x eines Strukturtyps gilt: unsafe.Alignof(x) ist der größte der Werte unsafe.Alignof(x.f) aller Felder f von x, mindestens aber 1.
  3. Für eine Variable x vom Typ Array gilt: unsafe.Alignof(x) ist gleich der Ausrichtung einer Variablen vom Typ des Map-Elementtyps.

Ein Struktur- oder Arraytyp hat die Größe null, wenn es keine Felder bzw. Elemente mit einer Größe größer null enthält. Zwei voneinander verschiedene Variablen der Größe null können dieselbe Speicheradresse haben.