Go 程式設計語言規範

語言版本 go1.25 (2025 年 2 月 25 日)

引言

這是 Go 程式語言的參考手冊。有關更多資訊和其他文件,請參閱 go.dev

Go 是一種通用語言,設計時考慮了系統程式設計。它強型別、垃圾回收,並明確支援併發程式設計。程式由構建而成,包的特性允許高效管理依賴項。

其語法緊湊且易於解析,允許整合開發環境等自動化工具輕鬆進行分析。

符號

語法使用擴充套件巴克斯-諾爾正規化 (EBNF) 的變體指定

Syntax      = { Production } .
Production  = production_name "=" [ Expression ] "." .
Expression  = Term { "|" Term } .
Term        = Factor { Factor } .
Factor      = production_name | token [ "…" token ] | Group | Option | Repetition .
Group       = "(" Expression ")" .
Option      = "[" Expression "]" .
Repetition  = "{" Expression "}" .

產生式是由項和以下運算子構造的表示式,優先順序遞增

|   alternation
()  grouping
[]  option (0 or 1 times)
{}  repetition (0 to n times)

小寫產生式名稱用於標識詞法(終端)標記。非終端使用駝峰命名法。詞法標記用雙引號""或反引號``括起來。

形式a … b表示從ab的一組字元作為備選。水平省略號也用於規範的其他地方,非正式地表示各種未進一步指定的列舉或程式碼片段。字元(與三個字元...相對)不是 Go 語言的標記。

形式為 [Go 1.xx] 的連結表示所描述的語言特性(或其某些方面)是在語言版本 1.xx 中更改或新增的,因此至少需要該語言版本才能構建。有關詳細資訊,請參閱附錄中的連結部分

源程式碼表示

原始碼是採用 UTF-8 編碼的 Unicode 文字。文字未規範化,因此單個帶重音的碼點與由重音和字母組合而成的相同字元不同;它們被視為兩個碼點。為簡單起見,本文件將使用不限定的術語字元來指代原始碼中的 Unicode 碼點。

每個碼點都是不同的;例如,大寫字母和小寫字母是不同的字元。

實現限制:為了與其他工具相容,編譯器可能會禁止原始碼中的 NUL 字元 (U+0000)。

實現限制:為了與其他工具相容,如果 UTF-8 編碼的位元組順序標記 (U+FEFF) 是原始碼中的第一個 Unicode 碼點,編譯器可能會忽略它。在原始碼的其他任何地方都可能禁止使用位元組順序標記。

字元

以下術語用於表示特定的 Unicode 字元類別

newline        = /* the Unicode code point U+000A */ .
unicode_char   = /* an arbitrary Unicode code point except newline */ .
unicode_letter = /* a Unicode code point categorized as "Letter" */ .
unicode_digit  = /* a Unicode code point categorized as "Number, decimal digit" */ .

Unicode 標準 8.0,第 4.5 節“通用類別”定義了一組字元類別。Go 將所有屬於字母類別 Lu、Ll、Lt、Lm 或 Lo 的字元視為 Unicode 字母,將屬於數字類別 Nd 的字元視為 Unicode 數字。

字母和數字

下劃線字元_ (U+005F) 被視為小寫字母。

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

詞法元素

Comments

註釋作為程式文件。有兩種形式

  1. 行註釋以字元序列//開頭,並在行尾結束。
  2. 通用註釋以字元序列/*開頭,並在第一個後續字元序列*/處結束。

註釋不能在符文字串字面量內部,或註釋內部開始。不包含換行符的通用註釋行為類似於空格。任何其他註釋行為類似於換行符。

標記

標記構成 Go 語言的詞彙。有四類:識別符號關鍵字運算子和標點符號以及字面量空白,由空格 (U+0020)、水平製表符 (U+0009)、回車符 (U+000D) 和換行符 (U+000A) 構成,除了分隔否則會組合成單個標記的標記外,其他情況都被忽略。此外,換行符或檔案結束可能會觸發分號的插入。在將輸入分解為標記時,下一個標記是形成有效標記的最長字元序列。

分號

形式語法在許多生成式中將分號";"用作終止符。Go 程式可以使用以下兩條規則省略大部分這些分號

  1. 當輸入被分解為標記時,如果行的最後一個標記是以下之一,則緊接著在該標記後自動插入分號到標記流中
  2. 為了允許複雜的語句佔據一行,在結束的")""}"之前可以省略分號。

為了反映慣用用法,本文件中的程式碼示例使用這些規則省略分號。

識別符號

識別符號命名程式實體,例如變數和型別。識別符號是由一個或多個字母和數字組成的序列。識別符號中的第一個字元必須是字母。

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

一些識別符號是預宣告的

關鍵字

以下關鍵字是保留的,不能用作識別符號。

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

運算子和標點符號

以下字元序列表示運算子(包括賦值運算子)和標點符號 [Go 1.18]

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

整數常量

整數常量是表示整數常量的數字序列。可選的字首設定非十進位制基數:0b0B表示二進位制,00o0O表示八進位制,0x0X表示十六進位制 [Go 1.13]。單個0被視為十進位制零。在十六進位制常量中,字母afAF表示值 10 到 15。

為了可讀性,下劃線字元_可以出現在基數字首之後或連續數字之間;此類下劃線不改變常量的取值。

int_lit        = decimal_lit | binary_lit | octal_lit | hex_lit .
decimal_lit    = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] .
binary_lit     = "0" ( "b" | "B" ) [ "_" ] binary_digits .
octal_lit      = "0" [ "o" | "O" ] [ "_" ] octal_digits .
hex_lit        = "0" ( "x" | "X" ) [ "_" ] hex_digits .

decimal_digits = decimal_digit { [ "_" ] decimal_digit } .
binary_digits  = binary_digit { [ "_" ] binary_digit } .
octal_digits   = octal_digit { [ "_" ] octal_digit } .
hex_digits     = hex_digit { [ "_" ] hex_digit } .
42
4_2
0600
0_600
0o600
0O600       // second character is capital letter 'O'
0xBadFace
0xBad_Face
0x_67_7a_2f_cc_40_c6
170141183460469231731687303715884105727
170_141183_460469_231731_687303_715884_105727

_42         // an identifier, not an integer literal
42_         // invalid: _ must separate successive digits
4__2        // invalid: only one _ at a time
0_xBadFace  // invalid: _ must separate successive digits

浮點字面量

浮點字面量是浮點常量的十進位制或十六進位制表示。

十進位制浮點字面量由整數部分(十進位制數字)、小數點、小數部分(十進位制數字)和指數部分(eE後跟可選符號和十進位制數字)組成。整數部分或小數部分之一可以省略;小數點或指數部分之一可以省略。指數值 exp 將尾數(整數和小數部分)乘以 10exp

十六進位制浮點字面量由0x0X字首、整數部分(十六進位制數字)、小數點、小數部分(十六進位制數字)和指數部分(pP後跟可選符號和十進位制數字)組成。整數部分或小數部分之一可以省略;小數點也可以省略,但指數部分是必需的。(此語法與 IEEE 754-2008 §5.12.3 中給出的語法匹配。)指數值 exp 將尾數(整數和小數部分)乘以 2exp [Go 1.13]。

為了可讀性,下劃線字元_可以出現在基數字首之後或連續數字之間;此類下劃線不改變字面量的值。

float_lit         = decimal_float_lit | hex_float_lit .

decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] |
                    decimal_digits decimal_exponent |
                    "." decimal_digits [ decimal_exponent ] .
decimal_exponent  = ( "e" | "E" ) [ "+" | "-" ] decimal_digits .

hex_float_lit     = "0" ( "x" | "X" ) hex_mantissa hex_exponent .
hex_mantissa      = [ "_" ] hex_digits "." [ hex_digits ] |
                    [ "_" ] hex_digits |
                    "." hex_digits .
hex_exponent      = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .
0.
72.40
072.40       // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5
1_5.         // == 15.0
0.15e+0_2    // == 15.0

0x1p-2       // == 0.25
0x2.p10      // == 2048.0
0x1.Fp+0     // == 1.9375
0X.8p-0      // == 0.5
0X_1FFFP-16  // == 0.1249847412109375
0x15e-2      // == 0x15e - 2 (integer subtraction)

0x.p1        // invalid: mantissa has no digits
1p-2         // invalid: p exponent requires hexadecimal mantissa
0x1.5e-2     // invalid: hexadecimal mantissa requires p exponent
1_.5         // invalid: _ must separate successive digits
1._5         // invalid: _ must separate successive digits
1.5_e1       // invalid: _ must separate successive digits
1.5e_1       // invalid: _ must separate successive digits
1.5e1_       // invalid: _ must separate successive digits

虛數字面量

虛數字面量表示複數常量的虛部。它由一個整數浮點字面量後跟小寫字母i組成。虛數字面量的值是相應整數或浮點字面量的值乘以虛數單位i [Go 1.13]

imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .

為了向後相容,虛數字面量的整數部分如果完全由十進位制數字(和可能的下劃線)組成,即使以開頭的0開頭,也被視為十進位制整數。

0i
0123i         // == 123i for backward-compatibility
0o123i        // == 0o123 * 1i == 83i
0xabci        // == 0xabc * 1i == 2748i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i
0x1p-2i       // == 0x1p-2 * 1i == 0.25i

符文字面量

符文字面量表示一個符文常量,一個標識 Unicode 碼點的整數值。符文字面量表示為一個或多個用單引號括起來的字元,如'x''\n'。在引號內,除了換行符和未轉義的單引號之外,任何字元都可以出現。單個引用字元表示字元本身的 Unicode 值,而以反斜槓開頭的多字元序列以各種格式編碼值。

最簡單的形式表示引號內的單個字元;由於 Go 原始碼是 UTF-8 編碼的 Unicode 字元,因此多個 UTF-8 編碼的位元組可能表示單個整數值。例如,字面量'a'包含一個表示字面量a的單位元組,Unicode U+0061,值為0x61,而'ä'包含兩個位元組(0xc3 0xa4),表示字面量 a-分音符,U+00E4,值為0xe4

幾個反斜槓轉義符允許將任意值編碼為 ASCII 文字。有四種方法可以將整數值表示為數值常量:\x後跟恰好兩個十六進位制數字;\u後跟恰好四個十六進位制數字;\U後跟恰好八個十六進位制數字,以及一個普通的反斜槓\後跟恰好三個八進位制數字。在每種情況下,字面量的值都是由相應基數中的數字表示的值。

儘管這些表示都產生一個整數,但它們有不同的有效範圍。八進位制轉義符必須表示 0 到 255(含)之間的值。十六進位制轉義符透過構造滿足此條件。轉義符\u\U表示 Unicode 碼點,因此其中某些值是非法的,特別是那些高於0x10FFFF和代理半部的。

在反斜槓之後,某些單字元轉義符表示特殊值

\a   U+0007 alert or bell
\b   U+0008 backspace
\f   U+000C form feed
\n   U+000A line feed or newline
\r   U+000D carriage return
\t   U+0009 horizontal tab
\v   U+000B vertical tab
\\   U+005C backslash
\'   U+0027 single quote  (valid escape only within rune literals)
\"   U+0022 double quote  (valid escape only within string literals)

符文字面量中反斜槓後面跟未識別的字元是非法的。

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'
'\''         // rune literal containing single quote character
'aa'         // illegal: too many characters
'\k'         // illegal: k is not recognized after a backslash
'\xa'        // illegal: too few hexadecimal digits
'\0'         // illegal: too few octal digits
'\400'       // illegal: octal value over 255
'\uDFFF'     // illegal: surrogate half
'\U00110000' // illegal: invalid Unicode code point

字串字面量

字串字面量表示透過連線字元序列獲得的字串常量。有兩種形式:原始字串字面量和解釋字串字面量。

原始字串字面量是反引號之間的字元序列,如`foo`。在引號內,除了反引號之外,任何字元都可以出現。原始字串字面量的值是由引號之間未解釋(隱式 UTF-8 編碼)字元組成的字串;特別是,反斜槓沒有特殊含義,字串可以包含換行符。原始字串字面量中的回車符 ('\r') 將從原始字串值中丟棄。

解釋字串字面量是雙引號之間的字元序列,例如"bar"。在引號內,除了換行符和未轉義的雙引號之外,任何字元都可以出現。引號之間的文字構成字面量的值,反斜槓轉義符的解釋方式與符文字面量相同(除了\'是非法的而\"是合法的),並具有相同的限制。三位八進位制 (\nnn) 和兩位十六進位制 (\xnn) 轉義符表示結果字串的單個位元組;所有其他轉義符表示單個字元的 (可能多位元組) UTF-8 編碼。因此,在字串字面量中,\377\xFF表示值為0xFF=255 的單個位元組,而ÿ\u00FF\U000000FF\xc3\xbf表示字元 U+00FF 的 UTF-8 編碼的兩個位元組0xc3 0xbf

string_lit             = raw_string_lit | interpreted_string_lit .
raw_string_lit         = "`" { unicode_char | newline } "`" .
interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc`                // same as "abc"
`\n
\n`                  // same as "\\n\n\\n"
"\n"
"\""                 // same as `"`
"Hello, world!\n"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"
"\uD800"             // illegal: surrogate half
"\U00110000"         // illegal: invalid Unicode code point

這些例子都表示相同的字串

"日本語"                                 // UTF-8 input text
`日本語`                                 // UTF-8 input text as a raw literal
"\u65e5\u672c\u8a9e"                    // the explicit Unicode code points
"\U000065e5\U0000672c\U00008a9e"        // the explicit Unicode code points
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"  // the explicit UTF-8 bytes

如果原始碼將字元表示為兩個碼點,例如涉及重音和字母的組合形式,則如果放在符文字面量中(它不是單個碼點),將導致錯誤,並且如果放在字串字面量中,將顯示為兩個碼點。

常量

布林常量符文常量整數常量浮點常量複數常量字串常量。符文、整數、浮點和複數常量統稱為數值常量

常量值由符文整數浮點虛數字串字面量、表示常量的識別符號、常量表達式、具有常量結果的轉換,或應用於常量引數的某些內建函式(如minmax)、應用於某些值unsafe.Sizeof、應用於某些表示式caplen、應用於複數常量的realimag以及應用於數值常量的complex的結果值表示。布林真值由預宣告常量truefalse表示。預宣告識別符號iota表示一個整數常量。

通常,複數常量是常量表達式的一種形式,並在該部分討論。

數值常量表示任意精度的精確值,並且不會溢位。因此,沒有表示 IEEE 754 負零、無窮大和非數字值的常量。

常量可以是有型別的無型別的。字面量常量、truefalseiota以及某些僅包含無型別常量運算元的常量表達式是無型別的。

常量可以透過常量宣告轉換顯式給定型別,或者在變數宣告賦值語句中使用時或作為表示式中的運算元時隱式給定型別。如果常量值不能表示為相應型別的值,則會出錯。如果型別是型別引數,則常量轉換為該型別引數的非常量值。

無型別常量具有預設型別,這是在需要有型別值的上下文中(例如,在沒有顯式型別的短變數宣告i := 0中)常量隱式轉換為的型別。無型別常量的預設型別分別是boolruneintfloat64complex128string,具體取決於它是布林、符文、整數、浮點、複數或字串常量。

實現限制:儘管數值常量在語言中具有任意精度,但編譯器可以使用內部有限精度表示來實現它們。儘管如此,每個實現都必須

這些要求適用於字面量常量和常量表達式的求值結果。

變數

變數是用於儲存的儲存位置。允許值的集合由變數的型別決定。

變數宣告,或對於函式引數和結果而言,函式宣告函式字面量的簽名,為命名變數保留儲存空間。呼叫內建函式new或獲取複合字面量的地址會在執行時為變數分配儲存空間。這樣的匿名變數透過(可能隱式的)指標間接引用。

陣列切片結構體型別的結構化變數具有可單獨定址的元素和欄位。每個此類元素都像一個變數。

變數的靜態型別(或簡稱型別)是其宣告中給定的型別、new呼叫或複合字面量中提供的型別,或者是結構化變數的元素的型別。介面型別的變數也具有不同的動態型別,它是執行時分配給變數的值的(非介面)型別(除非該值是預宣告識別符號nil,它沒有型別)。動態型別在執行期間可能有所不同,但儲存在介面變數中的值始終可賦值給變數的靜態型別。

var x interface{}  // x is nil and has static type interface{}
var v *T           // v has value nil, static type *T
x = 42             // x has value 42 and dynamic type int
x = v              // x has value (*T)(nil) and dynamic type *T

透過在表示式中引用變數來檢索變數的值;它是最近分配給變數的值。如果變數尚未分配值,則其值為其型別的零值

型別

型別決定了一組值以及特定於這些值的操作和方法。型別可以用型別名稱表示(如果有的話),如果型別是泛型,則其後必須跟型別引數。型別也可以使用型別字面量指定,它從現有型別組合型別。

Type     = TypeName [ TypeArgs ] | TypeLit | "(" Type ")" .
TypeName = identifier | QualifiedIdent .
TypeArgs = "[" TypeList [ "," ] "]" .
TypeList = Type { "," Type } .
TypeLit  = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
           SliceType | MapType | ChannelType .

語言預宣告了某些型別名稱。其他型別名稱則透過型別宣告型別引數列表引入。複合型別——陣列、結構體、指標、函式、介面、切片、對映和通道型別——可以使用型別字面量構造。

預定義型別、定義型別和型別引數稱為命名型別。如果別名宣告中給定的型別是命名型別,則別名表示命名型別。

布林型別

布林型別表示由預宣告常量truefalse表示的布林真值集。預宣告的布林型別是bool;它是一個定義型別

數字型別

整數浮點複數型別分別表示整數、浮點或複數值的集合。它們統稱為數值型別。預宣告的與架構無關的數值型別是

uint8       the set of all unsigned  8-bit integers (0 to 255)
uint16      the set of all unsigned 16-bit integers (0 to 65535)
uint32      the set of all unsigned 32-bit integers (0 to 4294967295)
uint64      the set of all unsigned 64-bit integers (0 to 18446744073709551615)

int8        the set of all signed  8-bit integers (-128 to 127)
int16       the set of all signed 16-bit integers (-32768 to 32767)
int32       the set of all signed 32-bit integers (-2147483648 to 2147483647)
int64       the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)

float32     the set of all IEEE 754 32-bit floating-point numbers
float64     the set of all IEEE 754 64-bit floating-point numbers

complex64   the set of all complex numbers with float32 real and imaginary parts
complex128  the set of all complex numbers with float64 real and imaginary parts

byte        alias for uint8
rune        alias for int32

n 位整數的值為 n 位寬,使用二進位制補碼算術表示。

還有一組預宣告的整數型別,其大小是實現特定的

uint     either 32 or 64 bits
int      same size as uint
uintptr  an unsigned integer large enough to store the uninterpreted bits of a pointer value

為了避免可移植性問題,所有數值型別都是定義型別,因此除了byte(它是uint8別名)和rune(它是int32的別名)之外,所有數值型別都是不同的。在表示式或賦值中混合不同的數值型別時,需要進行顯式轉換。例如,int32int不是相同的型別,即使它們在特定架構上可能具有相同的大小。

字串型別

字串型別表示字串值集。字串值是位元組的(可能為空)序列。位元組數稱為字串的長度,且永不為負。字串是不可變的:一旦建立,就無法更改字串的內容。預宣告的字串型別是string;它是一個定義型別

字串s的長度可以使用內建函式len獲取。如果字串是常量,則長度是編譯時常量。可以透過整數索引 0 到len(s)-1訪問字串的位元組。獲取此類元素的地址是非法的;如果s[i]是字串的第i個位元組,則&s[i]是無效的。

陣列型別

陣列是單一型別(稱為元素型別)元素的編號序列。元素的數量稱為陣列的長度,並且永不為負。

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

長度是陣列型別的一部分;它必須計算為可由int型別的值表示的非負常量。陣列a的長度可以使用內建函式len發現。元素可以透過整數索引 0 到len(a)-1定址。陣列型別總是二維的,但可以組合形成多維型別。

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

陣列型別T可能不具有型別為T的元素,或直接或間接包含T作為元件的型別(如果這些包含型別僅是陣列或結構體型別)。

// invalid array types
type (
	T1 [10]T1                 // element type of T1 is T1
	T2 [10]struct{ f T2 }     // T2 contains T2 as component of a struct
	T3 [10]T4                 // T3 contains T3 as component of a struct in T4
	T4 struct{ f T3 }         // T4 contains T4 as component of array T3 in a struct
)

// valid array types
type (
	T5 [10]*T5                // T5 contains T5 as component of a pointer
	T6 [10]func() T6          // T6 contains T6 as component of a function type
	T7 [10]struct{ f []T7 }   // T7 contains T7 as component of a slice in a struct
)

切片型別

切片是底層陣列的連續段的描述符,並提供對該陣列中編號元素序列的訪問。切片型別表示其元素型別的陣列的所有切片集合。元素的數量稱為切片的長度,永不為負。未初始化切片的值為nil

SliceType = "[" "]" ElementType .

切片s的長度可以透過內建函式len獲取;與陣列不同,它在執行期間可能會改變。元素可以透過整數索引 0 到len(s)-1進行定址。給定元素的切片索引可能小於同一元素在底層陣列中的索引。

切片一旦初始化,總是與持有其元素的底層陣列關聯。因此,切片與其陣列以及同一陣列的其他切片共享儲存空間;相比之下,不同的陣列總是表示不同的儲存空間。

切片底層的陣列可以超出切片的末尾。容量是該範圍的度量:它是切片長度與切片之外陣列長度的總和;可以透過從原始切片中切片一個新的切片來建立長度達到該容量的切片。切片a的容量可以使用內建函式cap(a)獲取。

可以使用內建函式make為給定元素型別T建立一個新的已初始化切片值,該函式接受切片型別和指定長度以及可選容量的引數。使用make建立的切片總是分配一個指向返回切片值的新隱藏陣列。也就是說,執行

make([]T, length, capacity)

生成與分配陣列並切片它相同的切片,因此這兩個表示式是等效的

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

與陣列一樣,切片始終是一維的,但可以組合以構造更高維度的物件。對於陣列的陣列,內部陣列在構造上始終具有相同的長度;但是對於切片的切片(或陣列的切片),內部長度可能會動態變化。此外,內部切片必須單獨初始化。

結構體型別

結構體是命名元素(稱為欄位)的序列,每個欄位都有名稱和型別。欄位名稱可以顯式指定(IdentifierList)或隱式指定(EmbeddedField)。在結構體中,非空白欄位名稱必須是唯一的

StructType    = "struct" "{" { FieldDecl ";" } "}" .
FieldDecl     = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ "*" ] TypeName [ TypeArgs ] .
Tag           = string_lit .
// An empty struct.
struct {}

// A struct with 6 fields.
struct {
	x, y int
	u float32
	_ float32  // padding
	A *[]int
	F func()
}

使用型別宣告但沒有顯式欄位名稱的欄位稱為嵌入欄位。嵌入欄位必須指定為型別名稱T或指向非介面型別名稱*T的指標,並且T本身不能是指標型別或型別引數。不合格的型別名稱充當欄位名稱。

// A struct with four embedded fields of types T1, *T2, P.T3 and *P.T4
struct {
	T1        // field name is T1
	*T2       // field name is T2
	P.T3      // field name is T3
	*P.T4     // field name is T4
	x, y int  // field names are x and y
}

以下宣告是非法的,因為欄位名稱在結構體型別中必須唯一

struct {
	T     // conflicts with embedded field *T and *P.T
	*T    // conflicts with embedded field T and *P.T
	*P.T  // conflicts with embedded field T and *T
}

結構體x中嵌入欄位的欄位或方法f被稱為提升,如果x.f是表示該欄位或方法f的合法選擇器

提升欄位的行為與結構體的普通欄位類似,只是它們不能用作結構體複合字面量中的欄位名稱。

給定結構體型別S和型別名稱T,提升方法包含在結構體的方法集中,如下所示

欄位聲明後可以跟一個可選的字串字面量標籤,它成為相應欄位宣告中所有欄位的屬性。空標籤字串等效於不存在的標籤。標籤透過反射介面可見,並參與結構體的型別標識,但除此之外都被忽略。

struct {
	x, y float64 ""  // an empty tag string is like an absent tag
	name string  "any string is permitted as a tag"
	_    [4]byte "ceci n'est pas un champ de structure"
}

// A struct corresponding to a TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers;
// they follow the convention outlined by the reflect package.
struct {
	microsec  uint64 `protobuf:"1"`
	serverIP6 uint64 `protobuf:"2"`
}

結構體型別T不能包含型別為T的欄位,或直接或間接包含T作為元件的型別(如果這些包含型別僅是陣列或結構體型別)。

// invalid struct types
type (
	T1 struct{ T1 }            // T1 contains a field of T1
	T2 struct{ f [10]T2 }      // T2 contains T2 as component of an array
	T3 struct{ T4 }            // T3 contains T3 as component of an array in struct T4
	T4 struct{ f [10]T3 }      // T4 contains T4 as component of struct T3 in an array
)

// valid struct types
type (
	T5 struct{ f *T5 }         // T5 contains T5 as component of a pointer
	T6 struct{ f func() T6 }   // T6 contains T6 as component of a function type
	T7 struct{ f [10][]T7 }    // T7 contains T7 as component of a slice in an array
)

指標型別

指標型別表示指向給定型別(稱為指標的基本型別變數的所有指標的集合。未初始化指標的nil

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

函式型別

函式型別表示所有具有相同引數和結果型別的函式的集合。未初始化函式型別變數的nil

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

在引數或結果列表中,名稱(IdentifierList)必須全部存在或全部缺失。如果存在,每個名稱代表指定型別的一個項(引數或結果),並且簽名中所有非空白名稱必須是唯一的。如果缺失,每個型別代表該型別的一個項。引數和結果列表總是用括號括起來,但如果只有一個未命名的結果,則可以將其寫為未帶括號的型別。

函式簽名中的最後一個傳入引數可以具有以...為字首的型別。具有此類引數的函式稱為可變引數函式,並且可以為該引數呼叫零個或多個引數。

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)

介面型別

介面型別定義一個型別集。介面型別的變數可以儲存介面型別集中任何型別的值。這種型別被稱為實現該介面。未初始化介面型別變數的nil

InterfaceType  = "interface" "{" { InterfaceElem ";" } "}" .
InterfaceElem  = MethodElem | TypeElem .
MethodElem     = MethodName Signature .
MethodName     = identifier .
TypeElem       = TypeTerm { "|" TypeTerm } .
TypeTerm       = Type | UnderlyingType .
UnderlyingType = "~" Type .

介面型別由介面元素列表指定。介面元素可以是方法型別元素,其中型別元素是一個或多個型別項的聯合。型別項可以是單個型別或單個底層型別。

基本介面

其最基本形式的介面指定了一個(可能為空)方法列表。由這樣的介面定義的型別集是實現所有這些方法的型別的集合,並且相應的方法集恰好由介面指定的方法組成。其型別集可以完全由方法列表定義的介面稱為基本介面

// A simple File interface.
interface {
	Read([]byte) (int, error)
	Write([]byte) (int, error)
	Close() error
}

每個顯式指定的方法的名稱必須是唯一的且非空白的

interface {
	String() string
	String() string  // illegal: String not unique
	_(x int)         // illegal: method must have non-blank name
}

一個介面可以被多個型別實現。例如,如果兩個型別S1S2具有方法集

func (p T) Read(p []byte) (n int, err error)
func (p T) Write(p []byte) (n int, err error)
func (p T) Close() error

(其中T代表S1S2),那麼File介面由S1S2都實現,無論S1S2可能擁有或共享其他什麼方法。

屬於介面型別集的所有型別都實現該介面。任何給定型別都可以實現多個不同的介面。例如,所有型別都實現表示所有(非介面)型別集合的空介面

interface{}

為方便起見,預宣告型別any是空介面的別名。[Go 1.18]

同樣,考慮這個介面規範,它出現在型別宣告中以定義名為Locker的介面

type Locker interface {
	Lock()
	Unlock()
}

如果S1S2也實現

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

它們既實現了Locker介面,也實現了File介面。

嵌入介面

在稍微更通用的形式中,介面T可以將(可能合格的)介面型別名稱E用作介面元素。這被稱為將介面E嵌入T中 [Go 1.14]。T的型別集是T顯式宣告的方法所定義的型別集與T嵌入介面的型別集的交集。換句話說,T的型別集是實現T所有顯式宣告方法以及E所有方法的所有型別的集合 [Go 1.18]。

type Reader interface {
	Read(p []byte) (n int, err error)
	Close() error
}

type Writer interface {
	Write(p []byte) (n int, err error)
	Close() error
}

// ReadWriter's methods are Read, Write, and Close.
type ReadWriter interface {
	Reader  // includes methods of Reader in ReadWriter's method set
	Writer  // includes methods of Writer in ReadWriter's method set
}

當嵌入介面時,具有相同名稱的方法必須具有相同的簽名。

type ReadCloser interface {
	Reader   // includes methods of Reader in ReadCloser's method set
	Close()  // illegal: signatures of Reader.Close and Close are different
}

通用介面

在其最一般形式中,介面元素也可以是任意型別項T,或形式為~T指定底層型別T的項,或項的並集t1|t2|…|tn [Go 1.18]。結合方法規範,這些元素能夠精確定義介面的型別集,如下所示

“所有非介面型別集合”的量化不僅指當前程式中宣告的所有(非介面)型別,而且指所有可能程式中所有可能型別,因此是無限的。同樣,給定實現特定方法的所有非介面型別集合,這些型別的方​​法集的交集將恰好包含該方法,即使當前程式中的所有型別總是將該方法與另一個方法配對。

根據構造,介面的型別集永遠不會包含介面型別。

// An interface representing only the type int.
interface {
	int
}

// An interface representing all types with underlying type int.
interface {
	~int
}

// An interface representing all types with underlying type int that implement the String method.
interface {
	~int
	String() string
}

// An interface representing an empty type set: there is no type that is both an int and a string.
interface {
	int
	string
}

~T形式的項中,T的底層型別必須是它本身,並且T不能是介面。

type MyInt int

interface {
	~[]byte  // the underlying type of []byte is itself
	~MyInt   // illegal: the underlying type of MyInt is not MyInt
	~error   // illegal: error is an interface
}

聯合元素表示型別集的聯合

// The Float interface represents all floating-point types
// (including any named types whose underlying types are
// either float32 or float64).
type Float interface {
	~float32 | ~float64
}

形式為T~T的項中的型別T不能是型別引數,並且所有非介面項的型別集必須兩兩不相交(型別集的兩兩交集必須為空)。給定型別引數P

interface {
	P                // illegal: P is a type parameter
	int | ~P         // illegal: P is a type parameter
	~int | MyInt     // illegal: the type sets for ~int and MyInt are not disjoint (~int includes MyInt)
	float32 | Float  // overlapping type sets but Float is an interface
}

實現限制:聯合(包含多個項)不能包含預宣告識別符號comparable或指定方法的介面,或嵌入comparable或指定方法的介面。

基本介面只能用作型別約束,或用作其他用作約束的介面的元素。它們不能是值或變數的型別,也不能是其他非介面型別的元件。

var x Float                     // illegal: Float is not a basic interface

var x interface{} = Float(nil)  // illegal

type Floatish struct {
	f Float                 // illegal
}

介面型別T不能直接或間接嵌入是、包含或嵌入T的型別元素。

// illegal: Bad may not embed itself
type Bad interface {
	Bad
}

// illegal: Bad1 may not embed itself using Bad2
type Bad1 interface {
	Bad2
}
type Bad2 interface {
	Bad1
}

// illegal: Bad3 may not embed a union containing Bad3
type Bad3 interface {
	~int | ~string | Bad3
}

// illegal: Bad4 may not embed an array containing Bad4 as element type
type Bad4 interface {
	[10]Bad4
}

實現介面

如果滿足以下條件之一,則型別T實現介面I

如果型別T實現介面,則型別T的值實現介面。

對映型別

對映是由一種型別(稱為元素型別)的元素組成的無序組,由另一種型別(稱為鍵型別)的一組唯一索引。未初始化對映的nil

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

鍵型別運算元必須完全定義比較運算子==!=;因此鍵型別不能是函式、對映或切片。如果鍵型別是介面型別,則必須為動態鍵值定義這些比較運算子;否則將導致執行時恐慌

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

對映元素的數量稱為其長度。對於對映m,可以使用內建函式len獲取其長度,並且長度在執行期間可能會改變。可以在執行期間使用賦值新增元素,並使用索引表示式檢索元素;可以使用deleteclear內建函式刪除元素。

可以使用內建函式make建立一個新的空對映值,該函式接受對映型別和可選的容量提示作為引數

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

初始容量不限制其大小:對映會隨著儲存專案數量的增加而增長,除了nil對映。nil對映等效於空對映,只是不能新增任何元素。

通道型別

通道提供了一種機制,用於併發執行的函式透過傳送接收指定元素型別的值進行通訊。未初始化通道的nil

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

可選的<-運算子指定通道的方向,即傳送接收。如果給定方向,通道是定向的,否則它是雙向的。通道可以透過賦值或顯式轉換限制為只能傳送或只能接收。

chan T          // can be used to send and receive values of type T
chan<- float64  // can only be used to send float64s
<-chan int      // can only be used to receive ints

<-運算子與最左邊的chan關聯

chan<- chan int    // same as chan<- (chan int)
chan<- <-chan int  // same as chan<- (<-chan int)
<-chan <-chan int  // same as <-chan (<-chan int)
chan (<-chan int)

可以使用內建函式make建立一個新的已初始化通道值,該函式接受通道型別和可選的容量作為引數

make(chan int, 100)

容量(以元素數量計)設定通道中緩衝區的大小。如果容量為零或缺失,則通道是無緩衝的,並且僅當傳送方和接收方都準備就緒時通訊才成功。否則,通道是緩衝的,並且如果緩衝區未滿(傳送)或不為空(接收),則通訊成功而不會阻塞。nil通道從不準備好通訊。

通道可以使用內建函式close關閉。接收運算子的多值賦值形式報告接收到的值是否在通道關閉之前傳送。

單個通道可以由任意數量的 goroutine 在傳送語句接收操作以及對內建函式caplen的呼叫中使用,而無需進一步同步。通道充當先進先出佇列。例如,如果一個 goroutine 在通道上傳送值,而第二個 goroutine 接收它們,則值將按傳送順序接收。

型別和值的屬性

值的表示

預宣告型別(參見下面的介面anyerror)、陣列和結構體的值是自包含的:每個這樣的值都包含其所有資料的完整副本,並且此類型別的變數儲存整個值。例如,陣列變數為陣列的所有元素提供儲存(變數)。各自的零值特定於值型別;它們從不為nil

非 nil 的指標、函式、切片、對映和通道值包含對底層資料的引用,這些資料可能由多個值共享

介面值可以是自包含的,也可以包含對底層資料的引用,具體取決於介面的動態型別。預宣告識別符號nil是其值可以包含引用的型別的零值。

當多個值共享底層資料時,改變一個值可能會改變另一個值。例如,改變切片的一個元素將改變所有共享該陣列的切片中底層陣列的該元素。

底層型別

每個型別T都有一個底層型別:如果T是預宣告的布林、數字或字串型別之一,或者是型別字面量,則相應的底層型別是T本身。否則,T的底層型別是T在其宣告中引用的型別的底層型別。對於型別引數,它是其型別約束的底層型別,它總是一個介面。

type (
	A1 = string
	A2 = A1
)

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

func f[P any](x P) { … }

stringA1A2B1B2的底層型別是string[]B1B3B4的底層型別是[]B1P的底層型別是interface{}

型別標識

兩種型別要麼是相同的(“相同”),要麼是不同的

命名型別總是與任何其他型別不同。否則,如果兩種型別的底層型別字面量結構等效,即它們具有相同的字面結構且相應元件具有相同的型別,則這兩種型別相同。詳細來說

給定宣告

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

	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

	C0 = B0
	D0[P1, P2 any] struct{ x P1; y P2 }
	E0 = D0[int, string]
)

這些型別是相同的

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

B0 and C0
D0[int, string] and E0
[]int and []int
struct{ a, b *B5 } and struct{ a, b *B5 }
func(x int, y float64) *[]string, func(int, float64) (result *[]string), and A5

B0B1不同,因為它們是由不同的型別定義建立的新型別;func(int, float64) *B0func(x int, y float64) *[]string不同,因為B0[]string不同;而P1P2不同,因為它們是不同的型別引數。D0[int, string]struct{ x int; y string }不同,因為前者是例項化的定義型別,而後者是型別字面量(但它們仍然可賦值)。

可賦值性

如果滿足以下條件之一,型別為V的值x可以賦值給型別為T變數(“x可賦值給T”)

此外,如果x的型別VT是型別引數,則如果滿足以下條件之一,x可賦值給型別為T的變數

可表示性

常量x可由型別T的值表示,其中T不是型別引數,如果滿足以下條件之一

如果T是型別引數,則如果x可由T型別集中的每個型別的值表示,則x可由型別T的值表示。

x                   T           x is representable by a value of T because

'a'                 byte        97 is in the set of byte values
97                  rune        rune is an alias for int32, and 97 is in the set of 32-bit integers
"foo"               string      "foo" is in the set of string values
1024                int16       1024 is in the set of 16-bit integers
42.0                byte        42 is in the set of unsigned 8-bit integers
1e10                uint64      10000000000 is in the set of unsigned 64-bit integers
2.718281828459045   float32     2.718281828459045 rounds to 2.7182817 which is in the set of float32 values
-1e-1000            float64     -1e-1000 rounds to IEEE -0.0 which is further simplified to 0.0
0i                  int         0 is an integer value
(42 + 0i)           float32     42.0 (with zero imaginary part) is in the set of float32 values
x                   T           x is not representable by a value of T because

0                   bool        0 is not in the set of boolean values
'a'                 string      'a' is a rune, it is not in the set of string values
1024                byte        1024 is not in the set of unsigned 8-bit integers
-1                  uint16      -1 is not in the set of unsigned 16-bit integers
1.1                 int         1.1 is not an integer value
42i                 float32     (0 + 42i) is not in the set of float32 values
1e1000              float64     1e1000 overflows to IEEE +Inf after rounding

方法集

型別的方法集決定了可以對該型別的運算元呼叫的方法。每個型別都關聯一個(可能為空的)方法集

適用於包含嵌入欄位的結構體(和指向結構體的指標)的更多規則在結構體型別一節中描述。任何其他型別都有一個空方法集。

在方法集中,每個方法必須具有唯一的空白方法名稱

是匹配花括號內的宣告和語句的(可能為空的)序列。

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

除了原始碼中的顯式塊之外,還有隱式塊

  1. 宇宙塊包含所有 Go 原始碼文字。
  2. 每個都有一個包塊,其中包含該包的所有 Go 原始碼文字。
  3. 每個檔案都有一個檔案塊,其中包含該檔案中的所有 Go 原始碼文字。
  4. 每個"if""for""switch"語句都被視為其自己的隱式塊。
  5. "switch""select"語句中的每個子句都充當隱式塊。

塊巢狀並影響作用域

宣告和作用域

宣告將非空白識別符號繫結到常量型別型別引數變數函式標籤。程式中的每個識別符號都必須宣告。任何識別符號都不能在同一個塊中宣告兩次,並且任何識別符號都不能同時在檔案塊和包塊中宣告。

空白識別符號可以在宣告中像任何其他識別符號一樣使用,但它不引入繫結,因此未宣告。在包塊中,識別符號init只能用於init函式宣告,並且像空白識別符號一樣,它不引入新的繫結。

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

宣告識別符號的作用域是原始碼文字的範圍,在該範圍中識別符號表示指定的常量、型別、變數、函式、標籤或包。

Go 使用進行詞法作用域

  1. 預宣告識別符號的作用域是宇宙塊。
  2. 頂層(函式外部)宣告的表示常量、型別、變數或函式(但不是方法)的識別符號的作用域是包塊。
  3. 匯入包的包名的作用域是包含匯入宣告的檔案的檔案塊。
  4. 表示方法接收器、函式引數或結果變數的識別符號的作用域是函式體。
  5. 表示函式型別引數或由方法接收器宣告的識別符號的作用域在函式名稱之後開始,並在函式體結束時結束。
  6. 表示型別型別引數的識別符號的作用域在型別名稱之後開始,並在 TypeSpec 結束時結束。
  7. 在函式內部宣告的常量或變數識別符號的作用域在 ConstSpec 或 VarSpec(短變數宣告的 ShortVarDecl)結束時開始,並在最內層包含塊結束時結束。
  8. 在函式內部宣告的型別識別符號的作用域在 TypeSpec 中的識別符號處開始,並在最內層包含塊結束時結束。

在塊中宣告的識別符號可以在內部塊中重新宣告。當內部宣告的識別符號在其作用域內時,它表示內部宣告的實體。

包子句不是宣告;包名不出現在任何作用域中。它的目的是標識屬於同一的檔案,併為匯入宣告指定預設包名。

標籤作用域

標籤由帶標籤的語句宣告,並用於"break""continue""goto"語句。定義從不使用的標籤是非法的。與其他識別符號相反,標籤不是塊作用域的,並且不與非標籤的識別符號衝突。標籤的作用域是其宣告函式的函式體,並排除任何巢狀函式的函式體。

空白識別符號

空白識別符號用下劃線字元_表示。它作為匿名佔位符而不是常規(非空白)識別符號,並且在宣告中、作為運算元以及在賦值語句中具有特殊含義。

預宣告識別符號

以下識別符號在宇宙塊中隱式宣告 [Go 1.18] [Go 1.21]

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

Constants:
	true false iota

Zero value:
	nil

Functions:
	append cap clear close complex copy delete imag len
	make max min new panic print println real recover

匯出識別符號

識別符號可以被匯出,以允許從另一個包訪問它。如果滿足以下兩個條件,則識別符號被匯出

  1. 識別符號名稱的第一個字元是 Unicode 大寫字母(Unicode 字元類別 Lu);並且
  2. 識別符號在包塊中宣告,或者它是欄位名稱方法名稱

所有其他識別符號均未匯出。

識別符號的唯一性

給定一組識別符號,如果一個識別符號與集合中的所有其他識別符號都不同,則稱該識別符號是唯一的。如果兩個識別符號拼寫不同,或者它們出現在不同的中且未匯出,則它們是不同的。否則,它們是相同的。

常量宣告

常量宣告將一個識別符號列表(常量名稱)繫結到常量表達式列表的值。識別符號的數量必須等於表示式的數量,並且左側的第n個識別符號繫結到右側的第n個表示式的值。

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

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

如果型別存在,所有常量都採用指定型別,並且表示式必須可賦值給該型別,該型別不能是型別引數。如果省略型別,常量將採用相應表示式的各個型別。如果表示式值是無型別的常量,則宣告的常量保持無型別,並且常量識別符號表示常量值。例如,如果表示式是浮點字面量,則常量識別符號表示浮點常量,即使字面量的小數部分為零。

const Pi float64 = 3.14159265358979323846
const zero = 0.0         // untyped floating-point constant
const (
	size int64 = 1024
	eof        = -1  // untyped integer constant
)
const a, b, c = 3, 4, "foo"  // a = 3, b = 4, c = "foo", untyped integer and string constants
const u, v float32 = 0, 3    // u = 0.0, v = 3.0

在帶括號的const宣告列表中,除了第一個 ConstSpec 外,表示式列表可以省略。這樣的空列表等效於文字替換前面第一個非空表示式列表及其型別(如果有)。因此,省略表示式列表等效於重複前一個列表。識別符號的數量必須等於前一個列表中的表示式數量。結合iota常量生成器,此機制允許輕量級地宣告序列值

const (
	Sunday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Partyday
	numberOfDays  // this constant is not exported
)

Iota

常量宣告中,預宣告識別符號iota表示連續的無型別整數常量。其值是該常量宣告中各自ConstSpec的索引,從零開始。它可用於構建一組相關常量

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

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

const (
	u         = iota * 42  // u == 0     (untyped integer constant)
	v float64 = iota * 42  // v == 42.0  (float64 constant)
	w         = iota * 42  // w == 84    (untyped integer constant)
)

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

根據定義,同一 ConstSpec 中iota的多次使用都具有相同的值

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

最後一個例子利用了最後一個非空表示式列表的隱式重複

型別宣告

型別宣告將識別符號(型別名稱)繫結到型別。型別宣告有兩種形式:別名宣告和型別定義。

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

別名宣告

別名宣告將識別符號繫結到給定型別 [Go 1.9]。

AliasDecl = identifier [ TypeParameters ] "=" Type .

在識別符號的作用域內,它充當給定型別的別名

type (
	nodeList = []*Node  // nodeList and []*Node are identical types
	Polar    = polar    // Polar and polar denote identical types
)

如果別名宣告指定型別引數 [Go 1.24],則型別名稱表示泛型別名。泛型別名在使用時必須例項化

type set[P comparable] = map[P]bool

在別名宣告中,給定型別不能是型別引數。

type A[P any] = P    // illegal: P is a type parameter

型別定義

型別定義建立一個新的、不同的型別,其底層型別和操作與給定型別相同,並將識別符號(型別名稱)繫結到它。

TypeDef = identifier [ TypeParameters ] Type .

新型別稱為定義型別。它與任何其他型別都不同,包括它所建立的型別。

type (
	Point struct{ x, y float64 }  // Point and struct{ x, y float64 } are different types
	polar Point                   // polar and Point denote different types
)

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

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

定義型別可以關聯方法。它不繼承繫結到給定型別的任何方法,但介面型別或複合型別元素的方法集保持不變

// A Mutex is a data type with two methods, Lock and Unlock.
type Mutex struct         { /* Mutex fields */ }
func (m *Mutex) Lock()    { /* Lock implementation */ }
func (m *Mutex) Unlock()  { /* Unlock implementation */ }

// NewMutex has the same composition as Mutex but its method set is empty.
type NewMutex Mutex

// The method set of PtrMutex's underlying type *Mutex remains unchanged,
// but the method set of PtrMutex is empty.
type PtrMutex *Mutex

// The method set of *PrintableMutex contains the methods
// Lock and Unlock bound to its embedded field Mutex.
type PrintableMutex struct {
	Mutex
}

// MyBlock is an interface type that has the same method set as Block.
type MyBlock Block

型別定義可用於定義不同的布林、數字或字串型別,並將方法與它們關聯

type TimeZone int

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

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

如果型別定義指定型別引數,則型別名稱表示泛型型別。泛型型別在使用時必須例項化

type List[T any] struct {
	next  *List[T]
	value T
}

在型別定義中,給定型別不能是型別引數。

type T[P any] P    // illegal: P is a type parameter

func f[T any]() {
	type L T   // illegal: T is a type parameter declared by the enclosing function
}

泛型型別也可以關聯方法。在這種情況下,方法接收者必須宣告與泛型型別定義中存在的型別引數數量相同的型別引數。

// The method Len returns the number of elements in the linked list l.
func (l *List[T]) Len() int  { … }

型別引數宣告

型別引數列表宣告泛型函式或型別宣告的型別引數。型別引數列表看起來像普通的函式引數列表,只是型別引數名稱必須全部存在,並且列表用方括號而不是圓括號括起來 [Go 1.18]。

TypeParameters = "[" TypeParamList [ "," ] "]" .
TypeParamList  = TypeParamDecl { "," TypeParamDecl } .
TypeParamDecl  = IdentifierList TypeConstraint .

列表中所有非空白名稱必須唯一。每個名稱都宣告一個型別引數,它是一個新的且不同的命名型別,在宣告中充當(目前為止)未知型別的佔位符。型別引數在泛型函式或型別例項化時被型別實參替換。

[P any]
[S interface{ ~[]byte|string }]
[S ~[]E, E any]
[P Constraint[int]]
[_ any]

正如每個普通函式引數都有一個引數型別一樣,每個型別引數都有一個相應的(元)型別,稱為其型別約束

當泛型型別的型別引數列表為一個通用型別宣告單個型別引數 P,並帶有一個約束 C,且文字 P C 構成一個有效的表示式時,就會出現解析歧義。

type T[P *C] …
type T[P (C)] …
type T[P *C|Q] …
…

在這些罕見情況下,型別引數列表與表示式無法區分,型別宣告被解析為陣列型別宣告。要解決這種歧義,請將約束嵌入到介面中或使用尾隨逗號。

type T[P interface{*C}] …
type T[P *C,] …

型別引數也可以由與泛型型別關聯的方法宣告的接收器規範宣告。

在泛型型別 T 的型別引數列表中,型別約束不得(直接或透過另一個泛型型別的型別引數列表間接)引用 T

type T1[P T1[P]] …                    // illegal: T1 refers to itself
type T2[P interface{ T2[int] }] …     // illegal: T2 refers to itself
type T3[P interface{ m(T3[int])}] …   // illegal: T3 refers to itself
type T4[P T5[P]] …                    // illegal: T4 refers to T5 and
type T5[P T4[P]] …                    //          T5 refers to T4

type T6[P int] struct{ f *T6[P] }     // ok: reference to T6 is not in type parameter list

型別約束

型別約束是一個介面,它定義了相應型別引數允許的型別實參集合,並控制該型別引數值的操作 [Go 1.18]。

TypeConstraint = TypeElem .

如果約束是一個 interface{E} 形式的介面字面量,其中 E 是一個嵌入的型別元素(而不是方法),那麼在型別引數列表中,為了方便起見,可以省略外層的 interface{ ... }

[T []P]                      // = [T interface{[]P}]
[T ~int]                     // = [T interface{~int}]
[T int|string]               // = [T interface{int|string}]
type Constraint ~int         // illegal: ~int is not in a type parameter list

預宣告的介面型別 comparable 表示所有嚴格可比較的非介面型別集合 [Go 1.18]。

儘管非型別引數的介面是可比較的,但它們不是嚴格可比較的,因此它們不實現 comparable。但是,它們滿足 comparable

int                          // implements comparable (int is strictly comparable)
[]byte                       // does not implement comparable (slices cannot be compared)
interface{}                  // does not implement comparable (see above)
interface{ ~int | ~string }  // type parameter only: implements comparable (int, string types are strictly comparable)
interface{ comparable }      // type parameter only: implements comparable (comparable implements itself)
interface{ ~int | ~[]byte }  // type parameter only: does not implement comparable (slices are not comparable)
interface{ ~struct{ any } }  // type parameter only: does not implement comparable (field any is not strictly comparable)

comparable 介面以及(直接或間接)嵌入 comparable 的介面只能用作型別約束。它們不能是值或變數的型別,也不能是其他非介面型別的組成部分。

滿足型別約束

如果型別實參 TC 定義的型別集的一個元素,則 T 滿足型別約束 C;換句話說,如果 T 實現 C。作為例外,嚴格可比較的型別約束也可以由可比較(不一定是嚴格可比較)的型別實參滿足 [Go 1.20]。更準確地說:

如果滿足以下條件,型別 T 滿足約束 C

type argument      type constraint                // constraint satisfaction

int                interface{ ~int }              // satisfied: int implements interface{ ~int }
string             comparable                     // satisfied: string implements comparable (string is strictly comparable)
[]byte             comparable                     // not satisfied: slices are not comparable
any                interface{ comparable; int }   // not satisfied: any does not implement interface{ int }
any                comparable                     // satisfied: any is comparable and implements the basic interface any
struct{f any}      comparable                     // satisfied: struct{f any} is comparable and implements the basic interface any
any                interface{ comparable; m() }   // not satisfied: any does not implement the basic interface interface{ m() }
interface{ m() }   interface{ comparable; m() }   // satisfied: interface{ m() } is comparable and implements the basic interface interface{ m() }

由於約束滿足規則中的例外情況,比較型別引數型別的值可能會在執行時引起 panic(即使可比較的型別引數始終是嚴格可比較的)。

變數宣告

變數宣告會建立一或多個變數,將相應的識別符號繫結到它們,併為每個變數指定型別和初始值。

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]  // map lookup; only interested in "found"

如果給定表示式列表,變數將按照賦值語句的規則進行初始化。否則,每個變數都將初始化為其零值

如果存在型別,則每個變數都將獲得該型別。否則,每個變數都將獲得賦值中相應初始化值的型別。如果該值是無型別常量,則它首先被隱式轉換為預設型別;如果它是一個無型別布林值,則它首先被隱式轉換為 bool 型別。預宣告識別符號 nil 不能用於初始化沒有顯式型別的變數。

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

實現限制:如果變數從未被使用,編譯器可能會禁止在函式體內宣告變數。

短變數宣告

短變數宣告使用以下語法:

ShortVarDecl = IdentifierList ":=" ExpressionList .

它是帶有初始化表示式但沒有型別的常規變數宣告的簡寫。

"var" IdentifierList "=" ExpressionList .
i, j := 0, 10
f := func() int { return 7 }
ch := make(chan int)
r, w, _ := os.Pipe()  // os.Pipe() returns a connected pair of Files and an error, if any
_, y, _ := coord(p)   // coord() returns three values; only interested in y coordinate

與常規變數宣告不同,短變數宣告可以重宣告變數,前提是它們最初在同一程式碼塊(如果程式碼塊是函式體,則在引數列表)中以相同型別宣告過,並且至少有一個非空白變數是新的。因此,重宣告只能出現在多變數短宣告中。重宣告不會引入新變數;它只是為原始變數賦新值。:= 左側的非空白變數名必須是唯一的

field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset)  // redeclares offset
x, y, x := 1, 2, 3                        // illegal: x repeated on left side of :=

短變數宣告只能出現在函式內部。在某些上下文中,例如 "if""for""switch" 語句的初始化器中,它們可以用於宣告區域性臨時變數。

函式宣告

函式宣告將一個識別符號(函式名)繫結到一個函式。

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

如果函式簽名聲明瞭結果引數,則函式體內的語句列表必須以終止語句結束。

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

如果函式宣告指定了型別引數,則函式名稱表示一個泛型函式。泛型函式必須在呼叫或用作值之前進行例項化

func min[T ~int|~float64](x, y T) T {
	if x < y {
		return x
	}
	return y
}

不帶型別引數的函式宣告可以省略函式體。此類宣告為在 Go 之外實現的函式(例如彙編例程)提供簽名。

func flushICache(begin, end uintptr)  // implemented externally

方法宣告

方法是帶接收器函式。方法宣告將一個識別符號(方法名)繫結到一個方法,並將該方法與接收器的基型別關聯起來。

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

接收器透過在方法名前面的額外引數部分指定。該引數部分必須宣告一個單一的非變長引數,即接收器。其型別必須是已定義的型別 T 或指向已定義型別 T 的指標,可能後跟方括號中包含的型別引數名稱列表 [P1, P2, ...]T 稱為接收器基型別。接收器基型別不能是指標或介面型別,並且必須與方法在同一包中定義。該方法被認為是繫結到其接收器基型別的,並且方法名僅在型別 T*T選擇器中可見。

空白接收器識別符號在方法簽名中必須是唯一的。如果接收器的值在方法體內部沒有被引用,則可以在宣告中省略其識別符號。這通常也適用於函式和方法的引數。

對於一個基型別,繫結到它的非空白方法名稱必須是唯一的。如果基型別是一個結構體型別,則非空白方法和欄位名稱必須不同。

給定已定義型別 Point,宣告如下:

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
}

將接收器型別為 *Point 的方法 LengthScale 繫結到基型別 Point

如果接收器基型別是一個泛型型別,則接收器規範必須宣告方法要使用的相應型別引數。這使得接收器型別引數可供方法使用。從語法上講,這種型別引數宣告看起來像接收器基型別的例項化:型別實參必須是表示正在宣告的型別引數的識別符號,接收器基型別的每個型別引數一個。型別引數名稱無需與其在接收器基型別定義中的相應引數名稱匹配,並且所有非空白引數名稱在接收器引數部分和方法簽名中必須是唯一的。接收器型別引數約束由接收器基型別定義隱含:相應的型別引數具有相應的約束。

type Pair[A, B any] struct {
	a A
	b B
}

func (p Pair[A, B]) Swap() Pair[B, A]  { … }  // receiver declares A, B
func (p Pair[First, _]) First() First  { … }  // receiver declares First, corresponds to A in Pair

如果接收器型別由(指向)別名表示,則該別名不得是泛型,並且不得表示已例項化的泛型型別,無論是直接還是透過另一個別名間接,也無論指標間接層數如何。

type GPoint[P any] = Point
type HPoint        = *GPoint[int]
type IPair         = Pair[int, int]

func (*GPoint[P]) Draw(P)   { … }  // illegal: alias must not be generic
func (HPoint) Draw(P)       { … }  // illegal: alias must not denote instantiated type GPoint[int]
func (*IPair) Second() int  { … }  // illegal: alias must not denote instantiated type Pair[int, int]

表示式

表示式透過將運算子和函式應用於運算元來指定值的計算。

運算元

運算元表示表示式中的基本值。運算元可以是字面量、表示常量變數函式的(可能限定的)非空白識別符號,或者帶括號的表示式。

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

表示泛型函式的運算元名稱後面可以跟一個型別實參列表;結果運算元是一個例項化的函式。

空白識別符號只能作為賦值語句的左側操作數出現。

實現限制:如果運算元的型別是具有空型別集型別引數,編譯器可能無需報告錯誤。帶有此類型別引數的函式無法例項化;任何嘗試都將在例項化站點導致錯誤。

限定識別符號

限定識別符號是用包名作為字首限定的識別符號。包名和識別符號都不能是空白的。

QualifiedIdent = PackageName "." identifier .

限定識別符號訪問不同包中的識別符號,該包必須被匯入。該識別符號必須是匯出的,並且在該包的包塊中宣告。

math.Sin // denotes the Sin function in package math

複合字面量

複合字面量在每次評估時為結構體、陣列、切片和對映構造新值。它們由字面量的型別後跟一個花括號括起來的元素列表組成。每個元素前面可以可選地帶有一個相應的鍵。

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

除非 LiteralType 是一個型別引數,否則其底層型別必須是結構體、陣列、切片或對映型別(語法強制執行此約束,除非型別以 TypeName 形式給出)。如果 LiteralType 是一個型別引數,則其型別集中的所有型別都必須具有相同的底層型別,且該底層型別必須是有效的複合字面量型別。元素和鍵的型別必須可賦值給型別 T 的相應欄位、元素和鍵型別;沒有額外的轉換。對於結構體字面量,鍵被解釋為欄位名;對於陣列和切片字面量,鍵被解釋為索引;對於對映字面量,鍵被解釋為鍵。對於對映字面量,所有元素都必須有一個鍵。指定多個具有相同欄位名或常量鍵值的元素是錯誤的。對於非常量對映鍵,請參閱評估順序部分。

對於結構體字面量,適用以下規則:

給定宣告

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

可以這樣寫:

origin := Point3D{}                            // zero value for Point3D
line := Line{origin, Point3D{y: -4, z: 12.3}}  // zero value for line.q.x

對於陣列和切片字面量,適用以下規則:

獲取複合字面量的地址會生成一個指向用字面量值初始化的唯一變數的指標。

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

請注意,切片或對映型別的零值與已初始化但為空的相同型別的值不同。因此,獲取空切片或映射覆合字面量的地址與使用 new 分配新的切片或對映值沒有相同的效果。

p1 := &[]int{}    // p1 points to an initialized, empty slice with value []int{} and length 0
p2 := new([]int)  // p2 points to an uninitialized slice with value nil and length 0

陣列字面量的長度是在字面量型別中指定的長度。如果字面量中提供的元素少於長度,則缺失的元素被設定為陣列元素型別的零值。提供索引值超出陣列索引範圍的元素是錯誤的。符號 ... 指定的陣列長度等於最大元素索引加一。

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

切片字面量描述了整個底層陣列字面量。因此,切片字面量的長度和容量是最大元素索引加一。切片字面量的形式為:

[]T{x1, x2, … xn}

它是應用於陣列的切片操作的簡寫:

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

在陣列、切片或對映型別 T 的複合字面量中,如果元素或對映鍵本身是複合字面量,並且其字面量型別與 T 的元素或鍵型別相同,則可以省略相應的字面量型別。類似地,如果元素或鍵是指向複合字面量的地址,並且元素或鍵型別是 *T,則可以省略 &T

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

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

當使用 TypeName 形式的 LiteralType 的複合字面量出現在“if”、“for”或“switch”語句的關鍵字和塊的左大括號之間作為運算元,並且複合字面量沒有用括號、方括號或花括號括起來時,就會出現解析歧義。在這種罕見情況下,字面量的左大括號被錯誤地解析為引入語句塊的左大括號。為了解決歧義,複合字面量必須出現在括號內。

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

有效陣列、切片和對映字面量的示例:

// list of prime numbers
primes := []int{2, 3, 5, 7, 9, 2147483647}

// vowels[ch] is true if ch is a vowel
vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}

// the 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}

// frequencies in Hz for equal-tempered scale (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,
}

函式字面量

函式字面量表示匿名函式。函式字面量不能宣告型別引數。

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

函式字面量可以賦值給變數或直接呼叫。

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

函式字面量是閉包:它們可以引用周圍函式中定義的變數。這些變數在周圍函式和函式字面量之間共享,並且只要它們可訪問就一直存在。

主表示式

主表示式是一元和二元表示式的運算元。

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()

選擇器

對於非包名主表示式 x選擇器表示式

x.f

表示值 x(或有時是 *x;見下文)的欄位或方法 f。識別符號 f 稱為(欄位或方法)選擇器;它不能是空白識別符號。選擇器表示式的型別是 f 的型別。如果 x 是包名,請參閱限定識別符號部分。

選擇器 f 可以表示型別 T 的欄位或方法 f,也可以引用 T 的巢狀嵌入欄位的欄位或方法 f。遍歷到達 f 所需的嵌入欄位數量稱為其在 T 中的深度。在 T 中宣告的欄位或方法 f 的深度為零。在 T 中嵌入欄位 A 中宣告的欄位或方法 f 的深度是 fA 中的深度加一。

以下規則適用於選擇器:

  1. 對於型別為 T*T 的值 x(其中 T 不是指標或介面型別),x.f 表示 T 中深度最淺的欄位或方法 f。如果深度最淺的 f 不止一個唯一的 f,則選擇器表示式是非法的。
  2. 對於型別為 I 的值 x(其中 I 是介面型別),x.f 表示 x 動態值的實際方法 f。如果 I方法集中沒有名為 f 的方法,則選擇器表示式是非法的。
  3. 作為例外,如果 x 的型別是已定義的指標型別,並且 (*x).f 是一個有效的選擇器表示式,表示一個欄位(而不是方法),則 x.f(*x).f 的簡寫。
  4. 在所有其他情況下,x.f 是非法的。
  5. 如果 x 是指標型別且值為 nil,並且 x.f 表示結構體欄位,則對 x.f 進行賦值或求值會導致執行時 panic
  6. 如果 x 是介面型別且值為 nil,則呼叫求值方法 x.f 會導致執行時 panic

例如,給定宣告:

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     // with t.T0 != nil
var p *T2    // with p != nil and (*p).T0 != nil
var q Q = p

可以這樣寫:

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

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

q.x          // (*(*q).T0).x        (*q).x is a valid field selector

p.M0()       // ((*p).T0).M0()      M0 expects *T0 receiver
p.M1()       // ((*p).T1).M1()      M1 expects T1 receiver
p.M2()       // p.M2()              M2 expects *T2 receiver
t.M2()       // (&t).M2()           M2 expects *T2 receiver, see section on Calls

但以下是無效的:

q.M0()       // (*q).M0 is valid but not a field selector

方法表示式

如果 M 位於型別 T方法集中,則 T.M 是一個函式,可以作為常規函式呼叫,其引數與 M 相同,但前面附加一個額外引數作為方法的接收器。

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

考慮一個結構體型別 T,它有兩個方法:Mv 的接收器型別為 TMp 的接收器型別為 *T

type T struct {
	a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

var t T

表示式

T.Mv

產生一個等同於 Mv 的函式,但其第一個引數是顯式接收器;其簽名是

func(tv T, a int) int

該函式可以用顯式接收器正常呼叫,因此這五種呼叫是等價的:

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

類似地,表示式

(*T).Mp

產生一個表示 Mp 的函式值,其簽名為

func(tp *T, f float32) float32

對於帶有值接收器的方法,可以派生一個帶有顯式指標接收器的函式,因此

(*T).Mv

產生一個表示 Mv 的函式值,其簽名為

func(tv *T, a int) int

這樣的函式透過接收器間接建立值,作為接收器傳遞給底層方法;該方法不會覆蓋函式呼叫中傳遞地址的值。

最後一種情況,即指標接收器方法的值接收器函式,是非法的,因為指標接收器方法不在值型別的方法集中。

從方法派生的函式值透過函式呼叫語法進行呼叫;接收器作為呼叫的第一個引數提供。也就是說,給定 f := T.Mvf 被呼叫為 f(t, 7) 而不是 t.f(7)。要構造繫結接收器的函式,請使用函式字面量方法值

從介面型別的方法派生函式值是合法的。生成的函式將該介面型別的顯式接收器作為引數。

方法值

如果表示式 x 的靜態型別為 TM 在型別 T方法集中,則 x.M 稱為方法值。方法值 x.M 是一個函式值,可以以與呼叫 x.M 方法相同的引數進行呼叫。表示式 x 在方法值求值期間被求值並儲存;儲存的副本隨後用作任何呼叫的接收器,這些呼叫可能稍後執行。

type S struct { *T }
type T int
func (t T) M() { print(t) }

t := new(T)
s := S{T: t}
f := t.M                    // receiver *t is evaluated and stored in f
g := s.M                    // receiver *(s.T) is evaluated and stored in g
*t = 42                     // does not affect stored receivers in f and g

型別 T 可以是介面型別或非介面型別。

如上文方法表示式討論中所述,考慮一個結構體型別 T,它有兩個方法:Mv 的接收器型別為 TMp 的接收器型別為 *T

type T struct {
	a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

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

表示式

t.Mv

產生一個函式值,其型別為

func(int) int

這兩個呼叫是等效的:

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

類似地,表示式

pt.Mp

產生一個函式值,其型別為

func(float32) float32

選擇器一樣,使用指標引用帶有值接收器的非介面方法將自動解引用該指標:pt.Mv 等同於 (*pt).Mv

方法呼叫一樣,使用可定址值引用帶有指標接收器的非介面方法將自動獲取該值的地址:t.Mp 等同於 (&t).Mp

f := t.Mv; f(7)   // like t.Mv(7)
f := pt.Mp; f(7)  // like pt.Mp(7)
f := pt.Mv; f(7)  // like (*pt).Mv(7)
f := t.Mp; f(7)   // like (&t).Mp(7)
f := makeT().Mp   // invalid: result of makeT() is not addressable

雖然上面的示例使用了非介面型別,但從介面型別的值建立方法值也是合法的。

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

索引表示式

形式為

a[x]

的主表示式表示陣列、指向陣列的指標、切片、字串或對映 a 中由 x 索引的元素。值 x 分別稱為索引對映鍵。以下規則適用:

如果 a 既不是對映也不是型別引數

對於陣列型別 Aa

對於指向陣列型別的 a

對於切片型別 Sa

對於字串型別a

對於對映型別 Ma

對於型別引數型別 Pa

否則 a[x] 是非法的。

賦值語句或特殊形式初始化中使用的對映 a(型別為 map[K]V)上的索引表示式

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

產生一個額外的無型別布林值。如果對映中存在鍵 x,則 ok 的值為 true,否則為 false

nil 對映的元素賦值會導致執行時 panic

切片表示式

切片表示式從字串、陣列、指向陣列的指標或切片運算元構造子字串或切片。有兩種變體:指定低位和高位邊界的簡單形式,以及還指定容量邊界的完整形式。

如果運算元型別是型別引數,除非其型別集包含字串型別,否則型別集中的所有型別都必須具有相同的底層型別,並且切片表示式必須對該型別的一個運算元有效。如果型別集包含字串型別,它也可以包含底層型別為 []byte 的位元組切片。在這種情況下,切片表示式必須對 string 型別的一個運算元有效。

簡單切片表示式

對於字串、陣列、指向陣列的指標或切片 a,主表示式

a[low : high]

構造一個子字串或切片。索引 lowhigh 選擇運算元 a 的哪些元素出現在結果中。結果的索引從 0 開始,長度等於 high - low。切片陣列 a 之後

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

切片 s 的型別為 []int,長度為 3,容量為 4,元素為

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

為方便起見,任何索引都可以省略。缺失的 low 索引預設為零;缺失的 high 索引預設為被切片運算元的長度。

a[2:]  // same as a[2 : len(a)]
a[:3]  // same as a[0 : 3]
a[:]   // same as a[0 : len(a)]

如果 a 是指向陣列的指標,則 a[low : high](*a)[low : high] 的簡寫。

對於陣列或字串,如果 0 <= low <= high <= len(a),則索引在範圍內,否則超出範圍。對於切片,上限索引是切片容量 cap(a) 而不是長度。常量索引必須是非負的,並且能夠由 int 型別的值表示;對於陣列或常量字串,常量索引也必須在範圍內。如果兩個索引都是常量,它們必須滿足 low <= high。如果索引在執行時超出範圍,則會發生執行時 panic

除了無型別字串,如果被切片的運算元是字串或切片,則切片操作的結果是一個與運算元相同型別的非常量值。對於無型別字串運算元,結果是一個 string 型別的非常量值。如果被切片的運算元是陣列,它必須是可定址的,並且切片操作的結果是一個與陣列具有相同元素型別的切片。

如果有效切片表示式的被切片運算元是 nil 切片,則結果是 nil 切片。否則,如果結果是切片,它與運算元共享其底層陣列。

var a [10]int
s1 := a[3:7]   // underlying array of s1 is array a; &s1[2] == &a[5]
s2 := s1[1:4]  // underlying array of s2 is underlying array of s1 which is array a; &s2[1] == &a[5]
s2[1] = 42     // s2[1] == s1[2] == a[5] == 42; they all refer to the same underlying array element

var s []int
s3 := s[:0]    // s3 == nil

完整切片表示式

對於陣列、指向陣列的指標或切片 a(但不是字串),主表示式

a[low : high : max]

構造一個切片,其型別、長度和元素與簡單切片表示式 a[low : high] 相同。此外,它透過將其設定為 max - low 來控制結果切片的容量。只有第一個索引可以省略;它預設為 0。切片陣列 a

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

切片 t 的型別為 []int,長度為 2,容量為 4,元素為

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

與簡單切片表示式類似,如果 a 是指向陣列的指標,則 a[low : high : max](*a)[low : high : max] 的簡寫。如果被切片的運算元是陣列,它必須是可定址的

如果 0 <= low <= high <= max <= cap(a),則索引在範圍內,否則它們超出範圍常量索引必須是非負的,並且能夠由 int 型別的值表示;對於陣列,常量索引也必須在範圍內。如果多個索引是常量,則存在的常量必須相互之間在範圍內。如果索引在執行時超出範圍,則會發生執行時 panic

型別斷言

對於介面型別的表示式 x(但不是型別引數)和型別 T,主表示式

x.(T)

斷言 x 不是 nil 並且 x 中儲存的值是型別 T。符號 x.(T) 稱為型別斷言

更準確地說,如果 T 不是介面型別,x.(T) 斷言 x 的動態型別與型別 T 相同。在這種情況下,T 必須實現 x 的(介面)型別;否則型別斷言無效,因為 x 不可能儲存型別 T 的值。如果 T 是介面型別,x.(T) 斷言 x 的動態型別實現介面 T

如果型別斷言成立,表示式的值是 x 中儲存的值,其型別是 T。如果型別斷言為假,則會發生執行時 panic。換句話說,儘管 x 的動態型別只有在執行時才知道,但在正確的程式中,x.(T) 的型別已知為 T

var x interface{} = 7          // x has dynamic type int and value 7
i := x.(int)                   // i has type int and value 7

type I interface { m() }

func f(y I) {
	s := y.(string)        // illegal: string does not implement I (missing method m)
	r := y.(io.Reader)     // r has type io.Reader and the dynamic type of y must implement both I and io.Reader
	…
}

賦值語句或特殊形式初始化中使用的型別斷言

v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok interface{} = x.(T) // dynamic types of v and ok are T and bool

產生一個額外的無型別布林值。如果斷言成立,ok 的值為 true。否則為 false,並且 v 的值為型別 T零值。在這種情況下不會發生執行時 panic

呼叫

給定一個函式型別 F 的表示式 f

f(a1, a2, … an)

用引數 a1, a2, … an 呼叫 f。除一種特殊情況外,引數必須是可賦值F 的引數型別的單值表示式,並在函式呼叫之前進行評估。表示式的型別是 F 的結果型別。方法呼叫類似,但方法本身被指定為接收器型別值的選擇器。

math.Atan2(x, y)  // function call
var pt *Point
pt.Scale(3.5)     // method call with receiver pt

如果 f 表示一個泛型函式,則必須先例項化它,然後才能呼叫或用作函式值。

如果 f 的型別是型別引數,則其型別集中的所有型別都必須具有相同的底層型別,該底層型別必須是函式型別,並且函式呼叫必須對該型別有效。

在函式呼叫中,函式值和引數按照通常的順序進行評估。評估完成後,為函式的變數(包括其引數和結果)分配新儲存。然後,呼叫的引數被傳遞給函式,這意味著它們被賦值給相應的函式引數,並且被呼叫的函式開始執行。函式返回時,函式的結果引數被傳回給呼叫者。

呼叫 nil 函式值會導致執行時 panic

作為一種特殊情況,如果函式或方法 g 的返回值數量相等且可以單獨賦值給另一個函式或方法 f 的引數,則呼叫 f(g(parameters_of_g)) 將在按順序將 g 的返回值傳遞給 f 的引數後呼叫 f。對 f 的呼叫除了對 g 的呼叫之外,不得包含其他引數,並且 g 必須至少有一個返回值。如果 f 有一個末尾的 ... 引數,則會將 g 的返回值在常規引數賦值後剩餘的部分賦值給它。

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("test fails")
}

如果 (x 的型別)的方法集包含 m 且引數列表可賦值給 m 的引數列表,則方法呼叫 x.m() 有效。如果 x 可定址&x 的方法集包含 m,則 x.m()(&x).m() 的簡寫。

var p Point
p.Scale(3.5)

沒有獨立的方法型別,也沒有方法字面量。

... 引數傳遞實參

如果 f可變引數的,且最終引數 p 的型別為 ...T,則在 f 內部,p 的型別等同於 []T。如果呼叫 f 時沒有為 p 提供實際引數,則傳遞給 p 的值為 nil。否則,傳遞的值是一個型別為 []T 的新切片,帶有一個新的底層陣列,其後續元素是實際引數,所有這些引數都必須可賦值T。因此,切片的長度和容量是繫結到 p 的引數數量,並且每次呼叫站點可能不同。

給定函式和呼叫

func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", "Anna", "Eileen")

Greeting 中,who 在第一次呼叫中將為 nil,在第二次呼叫中將為 []string{"Joe", "Anna", "Eileen"}

如果最終引數可賦值給切片型別 []T,並且後跟 ...,則它將作為 ...T 引數的值不變地傳遞。在這種情況下,不會建立新的切片。

給定切片 s 和呼叫

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

Greeting 中,who 將與 s 具有相同的值和相同的底層陣列。

例項化

透過將型別實參替換為型別引數來例項化泛型函式或型別 [Go 1.18]。例項化分兩步進行:

  1. 在泛型宣告中,每個型別實參都替換其對應的型別引數。此替換髮生在整個函式或型別宣告中,包括型別引數列表本身以及該列表中的任何型別。
  2. 替換後,每個型別實參必須滿足相應型別引數的約束(如果需要,也要例項化)。否則,例項化失敗。

例項化一個型別會產生一個新的非泛型命名型別;例項化一個函式會產生一個新的非泛型函式。

type parameter list    type arguments    after substitution

[P any]                int               int satisfies any
[S ~[]E, E any]        []int, int        []int satisfies ~[]int, int satisfies any
[P io.Writer]          string            illegal: string doesn't satisfy io.Writer
[P comparable]         any               any satisfies (but does not implement) comparable

在使用泛型函式時,型別實參可以顯式提供,也可以從函式使用的上下文中部分或完全推斷。如果可以推斷,在以下情況下可以完全省略型別實參列表:

在所有其他情況下,必須存在(可能部分)型別實參列表。如果型別實參列表缺失或部分缺失,則所有缺失的型別實參必須可從函式使用的上下文中推斷出來。

// sum returns the sum (concatenation, for strings) of its arguments.
func sum[T ~int | ~float64 | ~string](x... T) T { … }

x := sum                       // illegal: the type of x is unknown
intSum := sum[int]             // intSum has type func(x... int) int
a := intSum(2, 3)              // a has value 5 of type int
b := sum[float64](2.0, 3)      // b has value 5.0 of type float64
c := sum(b, -1)                // c has value 4.0 of type float64

type sumFunc func(x... string) string
var f sumFunc = sum            // same as var f sumFunc = sum[string]
f = sum                        // same as f = sum[string]

部分型別引數列表不能為空;至少第一個引數必須存在。該列表是完整型別引數列表的字首,剩餘引數待推斷。通俗地說,型別引數可以“從右到左”省略。

func apply[S ~[]E, E any](s S, f func(E) E) S { … }

f0 := apply[]                  // illegal: type argument list cannot be empty
f1 := apply[[]int]             // type argument for S explicitly provided, type argument for E inferred
f2 := apply[[]string, string]  // both type arguments explicitly provided

var bytes []byte
r := apply(bytes, func(byte) byte { … })  // both type arguments inferred from the function arguments

對於泛型型別,所有型別實參必須始終顯式提供。

型別推斷

如果泛型函式的一些或所有型別實參可以從函式使用的上下文(包括函式型別引數的約束)中推斷出來,則在使用泛型函式時可以省略這些型別實參。如果型別推斷能夠推斷出缺失的型別實參,並且使用推斷出的型別實參例項化成功,則型別推斷成功。否則,型別推斷失敗,程式無效。

型別推斷利用型別對之間的型別關係進行推斷:例如,函式實參必須可賦值給其相應的函式引數;這建立了實參型別與引數型別之間的關係。如果這兩種型別中的任何一種包含型別引數,則型別推斷會尋找型別實參來替換型別引數,以使可賦值關係得到滿足。類似地,型別推斷利用了型別實參必須滿足其相應型別引數的約束這一事實。

每對這樣的匹配型別都對應一個包含一個或多個型別引數的型別方程,這些型別引數可能來自一個或多個泛型函式。推斷缺失的型別引數意味著求解結果型別方程組中相應的型別引數。

例如,給定

// dedup returns a copy of the argument slice with any duplicate entries removed.
func dedup[S ~[]E, E comparable](S) S { … }

type Slice []int
var s Slice
s = dedup(s)   // same as s = dedup[Slice, int](s)

型別為 Slice 的變數 s 必須可賦值給函式引數型別 S,程式才能有效。為了降低複雜性,型別推斷忽略賦值的方向性,因此 SliceS 之間的型別關係可以透過(對稱的)型別方程 Slice ≡A S(或 S ≡A Slice)表示,其中 A 中的 A 表示 LHS 和 RHS 型別必須根據可賦值規則匹配(有關詳細資訊,請參閱型別統一部分)。類似地,型別引數 S 必須滿足其約束 ~[]E。這可以表示為 S ≡C ~[]E,其中 X ≡C Y 表示“X 滿足約束 Y”。這些觀察結果導致了一組兩個方程:

	Slice ≡A S      (1)
	S     ≡C ~[]E   (2)

現在可以求解型別引數 SE。從 (1) 編譯器可以推斷出 S 的型別實參是 Slice。類似地,由於 Slice 的底層型別是 []int 並且 []int 必須與約束的 []E 匹配,編譯器可以推斷出 E 必須是 int。因此,對於這兩個方程,型別推斷推斷出:

	S ➞ Slice
	E ➞ int

給定一組型別方程,要解決的型別引數是需要例項化且未提供顯式型別實參的函式的型別引數。這些型別引數稱為繫結型別引數。例如,在上面的 dedup 示例中,型別引數 SE 繫結到 dedup。泛型函式呼叫的實參本身可以是泛型函式。該函式的型別引數包含在繫結型別引數集中。函式實參的型別可能包含來自其他函式(例如包含函式呼叫的泛型函式)的型別引數。這些型別引數也可能出現在型別方程中,但在該上下文中它們未繫結。型別方程始終只針對繫結型別引數求解。

型別推斷支援泛型函式的呼叫以及泛型函式到(顯式函式型別)變數的賦值。這包括將泛型函式作為引數傳遞給其他(可能也是泛型)函式,以及將泛型函式作為結果返回。型別推斷針對這些情況中的每一種都操作一組特定的方程。方程如下(為清晰起見,省略了型別引數列表):

此外,每個型別引數 Pk 和相應的型別約束 Ck 產生型別方程 PkC Ck

型別推斷優先考慮從有型別運算元獲得的型別資訊,然後才考慮無型別常量。因此,推斷分兩個階段進行:

  1. 使用型別統一求解繫結型別引數的型別方程。如果統一失敗,則型別推斷失敗。

  2. 對於每個尚未推斷出型別實參且已收集到一個或多個具有相同型別引數的 (cj, Pk) 對的繫結型別引數 Pk,以與常量表達式相同的方式確定所有這些對中常量 cj常量種類Pk 的型別實參是確定的常量種類的預設型別。如果由於常量種類衝突而無法確定常量種類,則型別推斷失敗。

如果在這兩個階段之後仍未找到所有型別實參,則型別推斷失敗。

如果這兩個階段成功,則型別推斷確定了每個繫結型別引數的型別實參。

	Pk ➞ Ak

型別實參 Ak 可以是複合型別,包含其他繫結型別引數 Pk 作為元素型別(甚至只是另一個繫結型別引數)。在重複簡化的過程中,每個型別實參中的繫結型別引數將被相應的型別實參替換,直到每個型別實參不包含繫結型別引數。

如果型別實參透過繫結型別引數自身包含迴圈引用,則簡化和型別推斷失敗。否則,型別推斷成功。

型別統一

型別推斷透過型別統一來求解型別方程。型別統一遞迴地比較方程的 LHS 和 RHS 型別,其中任何一個或兩個型別都可以是或包含繫結的型別引數,並尋找這些型別引數的型別實參,以便 LHS 和 RHS 匹配(根據上下文變得相同或賦值相容)。為此,型別推斷維護一個從繫結型別引數到推斷型別實參的對映;在型別統一期間,會查詢並更新此對映。最初,繫結型別引數已知但對映為空。在型別統一期間,如果推斷出新的型別實參 A,則將相應的對映 P ➞ A 從型別引數到實參新增到對映中。相反,在比較型別時,已知型別實參(已存在對映條目的型別實參)取代其對應的型別引數。隨著型別推斷的進行,對映會越來越多地填充,直到所有方程都被考慮,或者直到統一失敗。如果沒有統一步驟失敗並且對映中每個型別引數都有一個條目,則型別推斷成功。

例如,給定帶有繫結型別引數 P 的型別方程:

	[10]struct{ elem P, list []P } ≡A [10]struct{ elem string; list []string }

型別推斷從空對映開始。統一首先比較 LHS 和 RHS 型別的頂層結構。兩者都是相同長度的陣列;如果元素型別統一,它們就統一。兩個元素型別都是結構體;如果它們具有相同數量的欄位和相同的名稱,並且欄位型別統一,它們就統一。P 的型別實參尚不清楚(沒有對映條目),因此將 Pstring 統一會將對映 P ➞ string 新增到對映中。統一 list 欄位的型別需要統一 []P[]string,從而統一 Pstring。由於此時 P 的型別實參已知(存在 P 的對映條目),其型別實參 string 取代 P。由於 stringstring 相同,因此此統一步驟也成功。方程 LHS 和 RHS 的統一現已完成。型別推斷成功,因為只有一個型別方程,沒有統一步驟失敗,並且對映已完全填充。

統一使用精確統一和寬鬆統一的組合,具體取決於兩種型別是否必須相同賦值相容或僅結構相等。具體的型別統一規則附錄中詳細說明。

對於 X ≡A Y 形式的方程,其中 XY 是涉及賦值(包括引數傳遞和返回語句)的型別,頂層型別結構可以寬鬆地統一,但元素型別必須精確統一,匹配賦值規則。

對於 P ≡C C 形式的方程,其中 P 是型別引數,C 是其對應的約束,統一規則稍微複雜一些:

在求解型別約束中的型別方程時,求解一個方程可能會推斷出額外的型別實參,這反過來可能會使依賴於這些型別實參的其他方程的求解成為可能。型別推斷會重複型別統一,只要推斷出新的型別實參。

運算子

運算子將運算元組合成表示式。

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   = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .

比較在其他地方討論。對於其他二元運算子,運算元型別必須相同,除非操作涉及移位或無型別常量。對於僅涉及常量的操作,請參閱常量表達式部分。

除了移位操作外,如果一個運算元是無型別常量而另一個運算元不是,則該常量會被隱式轉換為另一個運算元的型別。

移位表示式中的右運算元必須具有整數型別 [Go 1.13] 或可由 uint 型別值表示的無型別常量。如果非常量移位表示式的左運算元是無型別常量,它會首先被隱式轉換為如果移位表示式僅由其左運算元替換時它將假定的型別。

var a [1024]byte
var s uint = 33

// The results of the following examples are given for 64-bit ints.
var i = 1<<s                   // 1 has type int
var j int32 = 1<<s             // 1 has type int32; j == 0
var k = uint64(1<<s)           // 1 has type uint64; k == 1<<33
var m int = 1.0<<s             // 1.0 has type int; m == 1<<33
var n = 1.0<<s == j            // 1.0 has type int32; n == true
var o = 1<<s == 2<<s           // 1 and 2 have type int; o == false
var p = 1<<s == 1<<33          // 1 has type int; p == true
var u = 1.0<<s                 // illegal: 1.0 has type float64, cannot shift
var u1 = 1.0<<s != 0           // illegal: 1.0 has type float64, cannot shift
var u2 = 1<<s != 1.0           // illegal: 1 has type float64, cannot shift
var v1 float32 = 1<<s          // illegal: 1 has type float32, cannot shift
var v2 = string(1<<s)          // illegal: 1 is converted to a string, cannot shift
var w int64 = 1.0<<33          // 1.0<<33 is a constant shift expression; w == 1<<33
var x = a[1.0<<s]              // panics: 1.0 has type int, but 1<<33 overflows array bounds
var b = make([]byte, 1.0<<s)   // 1.0 has type int; len(b) == 1<<33

// The results of the following examples are given for 32-bit ints,
// which means the shifts will overflow.
var mm int = 1.0<<s            // 1.0 has type int; mm == 0
var oo = 1<<s == 2<<s          // 1 and 2 have type int; oo == true
var pp = 1<<s == 1<<33         // illegal: 1 has type int, but 1<<33 overflows int
var xx = a[1.0<<s]             // 1.0 has type int; xx == a[0]
var bb = make([]byte, 1.0<<s)  // 1.0 has type int; len(bb) == 0

運算子優先順序

一元運算子具有最高優先順序。由於 ++-- 運算子構成語句而非表示式,它們不屬於運算子層級結構。因此,語句 *p++ 等同於 (*p)++

二元運算子有五個優先順序級別。乘法運算子繫結最強,其次是加法運算子、比較運算子、&&(邏輯 AND),最後是 ||(邏輯 OR):

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

相同優先順序的二元運算子從左到右結合。例如,x / y * z(x / y) * z 相同。

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

算術運算子

算術運算子應用於數值,併產生與第一個運算元相同型別的結果。四個標準算術運算子(+-*/)適用於整數浮點複數型別;+ 也適用於字串。按位邏輯和移位運算子僅適用於整數。

+    sum                    integers, floats, complex values, strings
-    difference             integers, floats, complex values
*    product                integers, floats, complex values
/    quotient               integers, floats, complex values
%    remainder              integers

&    bitwise AND            integers
|    bitwise OR             integers
^    bitwise XOR            integers
&^   bit clear (AND NOT)    integers

<<   left shift             integer << integer >= 0
>>   right shift            integer >> integer >= 0

如果運算元型別是型別引數,則運算子必須應用於該型別集中的每個型別。運算元以型別引數例項化的型別實參的值表示,並且操作以該型別實參的精度計算。例如,給定函式:

func dotProduct[F ~float32|~float64](v1, v2 []F) F {
	var s F
	for i, x := range v1 {
		y := v2[i]
		s += x * y
	}
	return s
}

乘積 x * y 和加法 s += x * y 分別以 float32float64 精度計算,具體取決於 F 的型別實參。

整數運算子

對於兩個整數值 xy,整數商 q = x / y 和餘數 r = x % y 滿足以下關係:

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

x / y 截斷為零(“截斷除法”)。

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

此規則的一個例外是,如果被除數 xx 的 int 型別的最負值,則由於二補數整數溢位,商 q = x / -1 等於 x(且 r = 0):

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

如果除數為常量,則不能為零。如果在執行時除數為零,則會發生執行時 panic。如果被除數為非負數且除數為 2 的常數冪,則除法可以替換為右移,計算餘數可以替換為按位 AND 操作:

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

移位運算子將左運算元按右運算元指定的移位計數進行移位,右運算元必須是非負數。如果在執行時移位計數為負數,則會發生執行時 panic。如果左運算元是有符號整數,則移位運算子實現算術移位;如果左運算元是無符號整數,則實現邏輯移位。移位計數沒有上限。移位表現為左運算元對於移位計數 n 被移位 n 次,每次移位 1 位。因此,x << 1 等同於 x*2x >> 1 等同於 x/2,但向負無窮大截斷。

對於整數運算元,一元運算子 +-^ 定義如下:

+x                          is 0 + x
-x    negation              is 0 - x
^x    bitwise complement    is m ^ x  with m = "all bits set to 1" for unsigned x
                                      and  m = -1 for signed x

整數溢位

對於無符號整數值,操作 +-*<< 都在模 2n 的意義下進行計算,其中 n 是無符號整數型別的位寬。通俗地說,這些無符號整數操作在溢位時會丟棄高位,並且程式可以依賴“環繞”行為。

對於有符號整數,操作 +-*/<< 可能合法溢位,並且結果值存在,並由有符號整數表示、操作及其運算元確定地定義。溢位不會導致執行時 panic。編譯器不能假設溢位不會發生而最佳化程式碼。例如,它不能假設 x < x + 1 總是真。

浮點運算子

對於浮點數和複數,+xx 相同,而 -xx 的負值。浮點數或複數除以零的結果未在 IEEE 754 標準之外指定;是否發生執行時 panic 是實現相關的。

實現可以將多個浮點運算組合成一個融合運算,甚至跨語句,併產生一個與單獨執行和舍入指令獲得的值不同的結果。顯式浮點型別轉換會舍入到目標型別的精度,從而阻止會丟棄該舍入的融合。

例如,一些架構提供“融合乘加”(FMA)指令,該指令計算 x*y + z 而不捨入中間結果 x*y。這些示例顯示了 Go 實現何時可以使用該指令:

// FMA allowed for computing r, because x*y is not explicitly rounded:
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 disallowed for computing r, because it would omit rounding of x*y:
r  = float64(x*y) + z
r  = z; r += float64(x*y)
t  = float64(x*y); r = t + z

字串連線

字串可以使用 + 運算子或 += 賦值運算子進行連線:

s := "hi" + string(c)
s += " and good bye"

字串加法透過連線運算元建立新字串。

比較運算子

比較運算子比較兩個運算元併產生一個無型別布林值。

==    equal
!=    not equal
<     less
<=    less or equal
>     greater
>=    greater or equal

在任何比較中,第一個運算元必須可賦值給第二個運算元的型別,反之亦然。

相等運算子 ==!= 適用於可比較型別的運算元。排序運算子 <<=>>= 適用於有序型別的運算元。這些術語和比較結果定義如下:

如果具有相同動態型別的兩個介面值進行比較,並且該型別不可比較,則會發生執行時 panic。此行為不僅適用於直接介面值比較,還適用於比較介面值陣列或帶有介面值欄位的結構體。

切片、對映和函式型別不可比較。但是,作為特殊情況,切片、對映或函式值可以與預宣告識別符號 nil 進行比較。指標、通道和介面值與 nil 的比較也允許,並遵循上述一般規則。

const c = 3 < 4            // c is the untyped boolean constant true

type MyBool bool
var x, y int
var (
	// The result of a comparison is an untyped boolean.
	// The usual assignment rules apply.
	b3        = x == y // b3 has type bool
	b4 bool   = x == y // b4 has type bool
	b5 MyBool = x == y // b5 has type MyBool
)

如果型別可比較且不是介面型別,也不是由介面型別組成的,則該型別是嚴格可比較的。具體而言:

邏輯運算子

邏輯運算子應用於布林值,併產生與運算元相同型別的結果。首先評估左運算元,如果條件需要,再評估右運算元。

&&    conditional AND    p && q  is  "if p then q else false"
||    conditional OR     p || q  is  "if p then true else q"
!     NOT                !p      is  "not p"

地址運算子

對於型別為 T 的運算元 x,取地址操作 &x 生成一個型別為 *T 的指向 x 的指標。運算元必須是可定址的,即變數、指標間接定址或切片索引操作;或者是可定址結構體運算元的欄位選擇器;或者是可定址陣列的陣列索引操作。作為可定址性要求的一個例外,x 也可以是(可能帶括號的)複合字面量。如果 x 的求值會導致執行時 panic,那麼 &x 的求值也會。

對於指標型別 *T 的運算元 x,指標間接定址 *x 表示 x 指向的型別為 T變數。如果 xnil,嘗試評估 *x 將導致執行時 panic

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

var x *int = nil
*x   // causes a run-time panic
&*x  // causes a run-time panic

接收運算子

對於通道型別的運算數ch,接收操作<-ch的值是從通道ch接收到的值。通道方向必須允許接收操作,並且接收操作的型別是通道的元素型別。表示式會阻塞直到有值可用。從nil通道接收會永遠阻塞。在已關閉通道上執行接收操作可以立即進行,在接收完之前傳送的所有值後,將產生元素型別的零值

v1 := <-ch
v2 = <-ch
f(<-ch)
<-strobe  // wait until clock pulse and discard received value

如果運算數型別是型別引數,其型別集中的所有型別都必須是允許接收操作的通道型別,並且它們必須具有相同的元素型別,該元素型別就是接收操作的型別。

賦值語句或特殊形式的初始化中使用的接收表示式

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

會產生一個額外的無型別布林結果,報告通訊是否成功。如果收到的值是透過成功的傳送操作傳遞到通道的,則ok的值為true;如果通道已關閉且為空,從而生成了零值,則ok的值為false

型別轉換

型別轉換將表示式的型別更改為轉換指定的型別。型別轉換可以字面地出現在原始碼中,也可以由表示式出現的上下文隱式地進行。

顯式轉換是形如T(x)的表示式,其中T是一種型別,x是可以轉換為型別T的表示式。

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

如果型別以運算子*<-開頭,或者如果型別以關鍵字func開頭但沒有結果列表,則在必要時必須用括號括起來,以避免歧義

*Point(p)        // same as *(Point(p))
(*Point)(p)      // p is converted to *Point
<-chan int(c)    // same as <-(chan int(c))
(<-chan int)(c)  // c is converted to <-chan int
func()(x)        // function signature func() x
(func())(x)      // x is converted to func()
(func() int)(x)  // x is converted to func() int
func() int(x)    // x is converted to func() int (unambiguous)

如果常量x可以由T型別的值表示,則可以將其轉換為型別T。作為特例,整數常量x可以使用與非常量x相同的規則顯式轉換為字串型別

將常量轉換為不是型別引數的型別會產生一個有型別常量。

uint(iota)               // iota value of type uint
float32(2.718281828)     // 2.718281828 of type float32
complex128(1)            // 1.0 + 0.0i of type complex128
float32(0.49999999)      // 0.5 of type float32
float64(-1e-1000)        // 0.0 of type float64
string('x')              // "x" of type string
string(0x266c)           // "♬" of type string
myString("foo" + "bar")  // "foobar" of type myString
string([]byte{'a'})      // not a constant: []byte{'a'} is not a constant
(*int)(nil)              // not a constant: nil is not a constant, *int is not a boolean, numeric, or string type
int(1.2)                 // illegal: 1.2 cannot be represented as an int
string(65.0)             // illegal: 65.0 is not an integer constant

將常量轉換為型別引數會產生一個該型別的非常量值,該值表示為型別引數例項化時使用的型別實參的值。例如,給定函式

func f[P ~float32|~float64]() {
	… P(1.1) …
}

轉換P(1.1)會產生一個型別為P的非常量值,並且值1.1會根據f的型別實參表示為float32float64。因此,如果f使用float32型別例項化,表示式P(1.1) + 1.2的數值將以與對應的非常量float32加法相同的精度計算。

在以下任何情況下,非常量值x都可以轉換為型別T

此外,如果Tx的型別V是型別引數,則在以下任一條件適用時,x也可以轉換為型別T

在為轉換目的比較結構體型別的同一性時,結構體標籤將被忽略。

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)  // ignoring tags, the underlying types are identical

在數字型別之間或與字串型別之間進行(非常量)轉換時,適用特定規則。這些轉換可能會改變x的表示形式併產生執行時成本。所有其他轉換隻改變x的型別而不改變其表示形式。

沒有語言機制可以在指標和整數之間進行轉換。unsafe包在受限情況下實現了此功能。

數字型別之間的轉換

對於非常量數值的轉換,適用以下規則

  1. 整數型別之間轉換時,如果該值是有符號整數,則將其符號擴充套件到隱式無限精度;否則為零擴充套件。然後將其截斷以適應結果型別的大小。例如,如果v := uint16(0x10F0),則uint32(int8(v)) == 0xFFFFFFF0。轉換總是產生一個有效值;沒有溢位指示。
  2. 浮點數轉換為整數時,小數部分被丟棄(向零截斷)。
  3. 將整數或浮點數轉換為浮點型別,或將複數轉換為另一個複數型別時,結果值將四捨五入到目標型別指定的精度。例如,型別為float32的變數x的值可能以超出IEEE 754 32位數字的額外精度儲存,但float32(x)表示將x的值四捨五入到32位精度的結果。同樣,x + 0.1可能使用超過32位的精度,但float32(x + 0.1)則不會。

在所有涉及浮點數或複數的非常量轉換中,如果結果型別無法表示該值,則轉換成功,但結果值是實現定義的。

與字串型別之間的轉換

  1. 將位元組切片轉換為字串型別會產生一個字串,其連續位元組是切片的元素。
    string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'})   // "hellø"
    string([]byte{})                                     // ""
    string([]byte(nil))                                  // ""
    
    type bytes []byte
    string(bytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'})    // "hellø"
    
    type myByte byte
    string([]myByte{'w', 'o', 'r', 'l', 'd', '!'})       // "world!"
    myString([]myByte{'\xf0', '\x9f', '\x8c', '\x8d'})   // "🌍"
    
  2. 將rune切片轉換為字串型別會產生一個字串,該字串是轉換為字串的各個rune值的連線。
    string([]rune{0x767d, 0x9d6c, 0x7fd4})   // "\u767d\u9d6c\u7fd4" == "白鵬翔"
    string([]rune{})                         // ""
    string([]rune(nil))                      // ""
    
    type runes []rune
    string(runes{0x767d, 0x9d6c, 0x7fd4})    // "\u767d\u9d6c\u7fd4" == "白鵬翔"
    
    type myRune rune
    string([]myRune{0x266b, 0x266c})         // "\u266b\u266c" == "♫♬"
    myString([]myRune{0x1f30e})              // "\U0001f30e" == "🌎"
    
  3. 將字串型別的值轉換為位元組切片型別會產生一個非nil切片,其連續元素是字串的位元組。結果切片的容量是實現特定的,並且可能大於切片長度。
    []byte("hellø")             // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
    []byte("")                  // []byte{}
    
    bytes("hellø")              // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
    
    []myByte("world!")          // []myByte{'w', 'o', 'r', 'l', 'd', '!'}
    []myByte(myString("🌏"))    // []myByte{'\xf0', '\x9f', '\x8c', '\x8f'}
    
  4. 將字串型別的值轉換為rune切片型別會產生一個包含字串的各個Unicode碼點的切片。結果切片的容量是實現特定的,並且可能大於切片長度。
    []rune(myString("白鵬翔"))   // []rune{0x767d, 0x9d6c, 0x7fd4}
    []rune("")                  // []rune{}
    
    runes("白鵬翔")              // []rune{0x767d, 0x9d6c, 0x7fd4}
    
    []myRune("♫♬")              // []myRune{0x266b, 0x266c}
    []myRune(myString("🌐"))    // []myRune{0x1f310}
    
  5. 最後,出於歷史原因,整數值可以轉換為字串型別。這種形式的轉換會產生一個字串,其中包含給定整數值的Unicode碼點的(可能是多位元組)UTF-8表示。超出有效Unicode碼點範圍的值將被轉換為"\uFFFD"
    string('a')          // "a"
    string(65)           // "A"
    string('\xf8')       // "\u00f8" == "ø" == "\xc3\xb8"
    string(-1)           // "\ufffd" == "\xef\xbf\xbd"
    
    type myString string
    myString('\u65e5')   // "\u65e5" == "日" == "\xe6\x97\xa5"
    
    注意:這種形式的轉換最終可能會從語言中移除。go vet工具會將某些整數到字串的轉換標記為潛在錯誤。應改用諸如utf8.AppendRuneutf8.EncodeRune之類的庫函式。

切片到陣列或陣列指標的轉換

將切片轉換為陣列會產生一個包含切片底層陣列元素的陣列。同樣,將切片轉換為陣列指標會產生一個指向切片底層陣列的指標。在這兩種情況下,如果切片的長度小於陣列的長度,則會發生執行時panic

s := make([]byte, 2, 4)

a0 := [0]byte(s)
a1 := [1]byte(s[1:])     // a1[0] == s[1]
a2 := [2]byte(s)         // a2[0] == s[0]
a4 := [4]byte(s)         // panics: len([4]byte) > len(s)

s0 := (*[0]byte)(s)      // s0 != nil
s1 := (*[1]byte)(s[1:])  // &s1[0] == &s[1]
s2 := (*[2]byte)(s)      // &s2[0] == &s[0]
s4 := (*[4]byte)(s)      // panics: len([4]byte) > len(s)

var t []string
t0 := [0]string(t)       // ok for nil slice t
t1 := (*[0]string)(t)    // t1 == nil
t2 := (*[1]string)(t)    // panics: len([1]string) > len(t)

u := make([]byte, 0)
u0 := (*[0]byte)(u)      // u0 != nil

常量表達式

常量表達式只能包含常量運算數,並在編譯時求值。

無型別布林、數字和字串常量可以在任何允許使用布林、數字或字串型別運算數的地方用作運算數。

常量比較總是產生一個無型別布林常量。如果常量移位表示式的左運算數是無型別常量,則結果是整數常量;否則,它是與左運算數相同型別的常量,左運算數必須是整數型別

對無型別常量執行的任何其他操作都會產生相同種類的無型別常量;即布林、整數、浮點、複數或字串常量。如果二元運算(移位除外)的無型別運算數是不同種類,則結果是該列表中稍後出現的運算數種類:整數、rune、浮點、複數。例如,無型別整數常量除以無型別複數常量會產生無型別複數常量。

const a = 2 + 3.0          // a == 5.0   (untyped floating-point constant)
const b = 15 / 4           // b == 3     (untyped integer constant)
const c = 15 / 4.0         // c == 3.75  (untyped floating-point constant)
const Θ float64 = 3/2      // Θ == 1.0   (type float64, 3/2 is integer division)
const Π float64 = 3/2.     // Π == 1.5   (type float64, 3/2. is float division)
const d = 1 << 3.0         // d == 8     (untyped integer constant)
const e = 1.0 << 3         // e == 8     (untyped integer constant)
const f = int32(1) << 33   // illegal    (constant 8589934592 overflows int32)
const g = float64(2) >> 1  // illegal    (float64(2) is a typed floating-point constant)
const h = "foo" > "bar"    // h == true  (untyped boolean constant)
const j = true             // j == true  (untyped boolean constant)
const k = 'w' + 1          // k == 'x'   (untyped rune constant)
const l = "hi"             // l == "hi"  (untyped string constant)
const m = string(k)        // m == "x"   (type string)
const Σ = 1 - 0.707i       //            (untyped complex constant)
const Δ = Σ + 2.0e-4       //            (untyped complex constant)
const Φ = iota*1i - 1/1i   //            (untyped complex constant)

對無型別整數、rune或浮點常量應用內建函式complex會產生無型別複數常量。

const ic = complex(0, c)   // ic == 3.75i  (untyped complex constant)
const iΘ = complex(0, Θ)   // iΘ == 1i     (type complex128)

常量表達式總是精確求值;中間值和常量本身可能需要比語言中任何預宣告型別支援的精度大得多的精度。以下是合法的宣告

const Huge = 1 << 100         // Huge == 1267650600228229401496703205376  (untyped integer constant)
const Four int8 = Huge >> 98  // Four == 4                                (type int8)

常量除法或餘數運算的除數不能為零

3.14 / 0.0   // illegal: division by zero

有型別常量的值必須始終能夠由常量型別的值準確表示。以下常量表達式是非法的

uint(-1)     // -1 cannot be represented as a uint
int(3.14)    // 3.14 cannot be represented as an int
int64(Huge)  // 1267650600228229401496703205376 cannot be represented as an int64
Four * 300   // operand 300 cannot be represented as an int8 (type of Four)
Four * 100   // product 400 cannot be represented as an int8 (type of Four)

一元按位補碼運算子^使用的掩碼與非常量的規則匹配:對於無符號常量,掩碼全部為1;對於有符號和無型別常量,掩碼為-1。

^1         // untyped integer constant, equal to -2
uint8(^1)  // illegal: same as uint8(-2), -2 cannot be represented as a uint8
^uint8(1)  // typed uint8 constant, same as 0xFF ^ uint8(1) = uint8(0xFE)
int8(^1)   // same as int8(-2)
^int8(1)   // same as -1 ^ int8(1) = -2

實現限制:編譯器在計算無型別浮點或複數常量表達式時可能會使用舍入;請參閱常量一節中的實現限制。這種舍入可能會導致浮點常量表達式在整數上下文中無效,即使它在使用無限精度計算時是整數,反之亦然。

求值順序

在包級別,初始化依賴決定了變數宣告中各個初始化表示式的求值順序。否則,在求值表示式、賦值或返回語句運算數時,所有函式呼叫、方法呼叫、接收操作二元邏輯操作都按詞法從左到右的順序求值。

例如,在(函式區域性)賦值中

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

函式呼叫和通訊按f()h()(如果z求值為false)、i()j()<-cg()k()的順序發生。但是,這些事件與x的求值和索引以及yz的求值相比的順序未指定,除非詞法要求。例如,g不能在其引數求值之前呼叫。

a := 1
f := func() int { a++; return a }
x := []int{a, f()}            // x may be [1, 2] or [2, 2]: evaluation order between a and f() is not specified
m := map[int]int{a: 1, a: 2}  // m may be {2: 1} or {2: 2}: evaluation order between the two map assignments is not specified
n := map[int]int{a: f()}      // n may be {2: 3} or {3: 3}: evaluation order between the key and the value is not specified

在包級別,初始化依賴會覆蓋單個初始化表示式的從左到右規則,但不會覆蓋每個表示式內的運算數

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 }

// functions u and v are independent of all other variables and functions

函式呼叫按u()sqr()v()f()v()g()的順序發生。

單個表示式中的浮點運算根據運算子的結合性進行求值。顯式括號透過覆蓋預設結合性來影響求值。在表示式x + (y + z)中,加法y + z在加x之前執行。

語句

語句控制執行。

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

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

終止語句

終止語句中斷程式碼塊中正常的控制流。以下語句是終止語句

  1. 一個"return""goto"語句。
  2. 對內建函式panic的呼叫。
  3. 語句列表以終止語句結尾的程式碼塊
  4. 一個"if"語句,其中
    • "else"分支存在,並且
    • 兩個分支都是終止語句。
  5. 一個"for"語句,其中
    • 沒有引用該"for"語句的"break"語句,並且
    • 迴圈條件缺失,並且
    • "for"語句不使用range子句。
  6. 一個"switch"語句,其中
    • 沒有引用該"switch"語句的"break"語句,
    • 存在一個default情況,並且
    • 每個情況(包括default)的語句列表以終止語句或可能是帶標籤的"fallthrough"語句結尾。
  7. 一個"select"語句,其中
    • 沒有引用該"select"語句的"break"語句,並且
    • 每個情況(包括default,如果存在)的語句列表以終止語句結尾。
  8. 標記了終止語句的帶標籤語句

所有其他語句都不是終止語句。

如果語句列表不為空且其最後一個非空語句是終止語句,則該語句列表以終止語句結尾。

空語句

空語句不執行任何操作。

EmptyStmt = .

帶標籤語句

帶標籤語句可以是gotobreakcontinue語句的目標。

LabeledStmt = Label ":" Statement .
Label       = identifier .
Error: log.Panic("error encountered")

表示式語句

除特定內建函式外,函式和方法呼叫以及接收操作可以出現在語句上下文中。此類語句可以加括號。

ExpressionStmt = Expression .

以下內建函式不允許出現在語句上下文中

append cap complex imag len make new real
unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice unsafe.SliceData unsafe.String unsafe.StringData
h(x+y)
f.Close()
<-ch
(<-ch)
len("foo")  // illegal if len is the built-in function

傳送語句

傳送語句在通道上傳送一個值。通道表示式必須是通道型別,通道方向必須允許傳送操作,並且要傳送的值的型別必須可以賦值給通道的元素型別。

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

通道和值表示式在通訊開始之前都會被求值。通訊會阻塞直到傳送可以進行。如果接收方準備就緒,無緩衝通道上的傳送可以進行。如果緩衝區中有空間,有緩衝通道上的傳送可以進行。在已關閉通道上的傳送會透過導致執行時panic來繼續。在nil通道上的傳送會永遠阻塞。

ch <- 3  // send value 3 to channel ch

如果通道表示式的型別是型別引數,其型別集中的所有型別都必須是允許傳送操作的通道型別,它們必須具有相同的元素型別,並且要傳送的值的型別必須可以賦值給該元素型別。

增減語句

"++"和"--"語句將它們的運算數增加或減少無型別常量1。與賦值一樣,運算數必須是可定址的或對映索引表示式。

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

以下賦值語句在語義上是等效的

IncDec statement    Assignment
x++                 x += 1
x--                 x -= 1

賦值語句

賦值表示式指定的新值替換儲存在變數中的當前值。賦值語句可以將單個值賦給單個變數,或將多個值賦給匹配數量的變數。

Assignment = ExpressionList assign_op ExpressionList .

assign_op  = [ add_op | mul_op ] "=" .

每個左側運算數必須是可定址的、對映索引表示式,或(僅適用於=賦值)空白識別符號。運算數可以加括號。

x = 1
*p = f()
a[i] = 23
(k) = <-ch  // same as: k = <-ch

賦值操作x op= y,其中op是二元算術運算子,等價於x = x op (y),但只對x求值一次。op=構造是一個單獨的詞元。在賦值操作中,左側和右側表示式列表必須都包含恰好一個單值表示式,並且左側表示式不能是空白識別符號。

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

元組賦值將多值操作的各個元素賦值給變數列表。有兩種形式。第一種形式中,右側運算數是單個多值表示式,例如函式呼叫、通道對映操作,或型別斷言。左側運算數的數量必須與值的數量匹配。例如,如果f是一個返回兩個值的函式,

x, y = f()

將第一個值賦給x,第二個值賦給y。第二種形式中,左側運算數的數量必須等於右側表示式的數量,每個表示式必須是單值的,並且右側的第n個表示式賦給左側的第n個運算數

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

空白識別符號提供了一種在賦值中忽略右側值的方法

_ = x       // evaluate x but ignore it
x, _ = f()  // evaluate f() but ignore second result value

賦值分兩個階段進行。首先,左側的索引表示式指標間接引用(包括選擇器中的隱式指標間接引用)的運算數以及右側的表示式都按常規順序求值。其次,賦值按從左到右的順序執行。

a, b = b, a  // exchange a and b

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

i = 0
x[i], i = 2, 1  // set x[0] = 2, i = 1

x[0], x[0] = 1, 2  // set x[0] = 1, then x[0] = 2 (so x[0] == 2 at end)

x[1], x[3] = 4, 5  // set x[1] = 4, then panic setting x[3] = 5.

type Point struct { x, y int }
var p *Point
x[2], p.x = 6, 7  // set x[2] = 6, then panic setting p.x = 7

i = 2
x = []int{3, 5, 7}
for i, x[i] = range x {  // set i, x[2] = 0, x[0]
	break
}
// after this loop, i == 0 and x is []int{3, 5, 3}

在賦值中,每個值必須可以賦值給它所賦值的運算數的型別,並具有以下特殊情況

  1. 任何有型別的值都可以賦值給空白識別符號。
  2. 如果無型別常量賦值給介面型別變數或空白識別符號,常量首先會被隱式轉換為其預設型別
  3. 如果無型別布林值賦值給介面型別變數或空白識別符號,它首先會被隱式轉換為bool型別。

當一個值被賦給一個變數時,只有儲存在變數中的資料被替換。如果該值包含一個引用,賦值會複製該引用,但不會複製被引用的資料(例如切片的底層陣列)。

var s1 = []int{1, 2, 3}
var s2 = s1                    // s2 stores the slice descriptor of s1
s1 = s1[:1]                    // s1's length is 1 but it still shares its underlying array with s2
s2[0] = 42                     // setting s2[0] changes s1[0] as well
fmt.Println(s1, s2)            // prints [42] [42 2 3]

var m1 = make(map[string]int)
var m2 = m1                    // m2 stores the map descriptor of m1
m1["foo"] = 42                 // setting m1["foo"] changes m2["foo"] as well
fmt.Println(m2["foo"])         // prints 42

If 語句

"If"語句根據布林表示式的值指定兩個分支的條件執行。如果表示式求值為真,則執行"if"分支;否則,如果存在"else"分支,則執行"else"分支。

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

表示式前面可以帶有一個簡單語句,該語句在表示式求值之前執行。

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

Switch 語句

"Switch"語句提供多路執行。表示式或型別與"switch"中的"case"進行比較,以確定執行哪個分支。

SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .

有兩種形式:表示式switch和型別switch。在表示式switch中,case包含與switch表示式的值進行比較的表示式。在型別switch中,case包含與特殊標註的switch表示式的型別進行比較的型別。switch表示式在switch語句中只求值一次。

表示式switch

在表示式switch中,switch表示式被求值,case表示式(不一定是常量)按從左到右、從上到下的順序求值;第一個等於switch表示式的會觸發執行關聯case的語句;其他case會被跳過。如果沒有case匹配且存在"default"情況,則執行其語句。最多隻能有一個default情況,並且它可以出現在"switch"語句中的任何位置。缺失的switch表示式等價於布林值true

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

如果switch表示式求值為無型別常量,它會首先被隱式轉換為其預設型別。預宣告的無型別值nil不能用作switch表示式。switch表示式型別必須是可比較的

如果case表示式是無型別的,它會首先被隱式轉換為switch表示式的型別。對於每個(可能已轉換的)case表示式x和switch表示式的值tx == t必須是有效的比較

換句話說,switch表示式被視為用於宣告和初始化一個沒有顯式型別的臨時變數t;每個case表示式x都是與t的該值進行相等性測試。

在case或default子句中,最後一個非空語句可以是(可能帶標籤的"fallthrough"語句,表示控制流應從該子句的末尾流向下一個子句的第一個語句。否則,控制流將流向"switch"語句的末尾。"fallthrough"語句可以作為表示式switch除最後一個子句之外的所有子句的最後一個語句出現。

switch表示式前面可以帶有一個簡單語句,該語句在表示式求值之前執行。

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

switch x := f(); {  // missing switch expression means "true"
case x < 0: return -x
default: return x
}

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

實現限制:編譯器可能不允許多個case表示式求值為相同的常量。例如,當前的編譯器不允許case表示式中出現重複的整數、浮點或字串常量。

型別switch

型別switch比較型別而不是值。它與表示式switch類似。它由一個特殊的switch表示式標記,該表示式具有型別斷言的形式,使用關鍵字type而不是實際型別

switch x.(type) {
// cases
}

然後,case將實際型別T與表示式x的動態型別進行匹配。與型別斷言一樣,x必須是介面型別,但不能是型別引數,並且case中列出的每個非介面型別T都必須實現x的型別。型別switch的case中列出的型別必須都不同

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

TypeSwitchGuard可以包含一個短變數宣告。當使用這種形式時,變數在每個子句的隱式塊中,在TypeSwitchCase的末尾宣告。在只列出一個型別的子句中,變數具有該型別;否則,變數具有TypeSwitchGuard中表達式的型別。

除了型別,case還可以使用預宣告識別符號nil;當TypeSwitchGuard中的表示式是nil介面值時,會選擇該case。最多隻能有一個nilcase。

給定一個型別為interface{}的表示式x,以下型別switch

switch i := x.(type) {
case nil:
	printString("x is nil")                // type of i is type of x (interface{})
case int:
	printInt(i)                            // type of i is int
case float64:
	printFloat64(i)                        // type of i is float64
case func(int) float64:
	printFunction(i)                       // type of i is func(int) float64
case bool, string:
	printString("type is bool or string")  // type of i is type of x (interface{})
default:
	printString("don't know the type")     // type of i is type of x (interface{})
}

可以重寫為

v := x  // x is evaluated exactly once
if v == nil {
	i := v                                 // type of i is type of x (interface{})
	printString("x is nil")
} else if i, isInt := v.(int); isInt {
	printInt(i)                            // type of i is int
} else if i, isFloat64 := v.(float64); isFloat64 {
	printFloat64(i)                        // type of i is float64
} else if i, isFunc := v.(func(int) float64); isFunc {
	printFunction(i)                       // type of i is func(int) float64
} else {
	_, isBool := v.(bool)
	_, isString := v.(string)
	if isBool || isString {
		i := v                         // type of i is type of x (interface{})
		printString("type is bool or string")
	} else {
		i := v                         // type of i is type of x (interface{})
		printString("don't know the type")
	}
}

型別引數泛型型別可以用作case中的型別。如果在例項化時該型別與switch中的另一個條目重複,則選擇第一個匹配的case。

func f[P any](x any) int {
	switch x.(type) {
	case P:
		return 0
	case string:
		return 1
	case []P:
		return 2
	case []byte:
		return 3
	default:
		return 4
	}
}

var v1 = f[string]("foo")   // v1 == 0
var v2 = f[byte]([]byte{})  // v2 == 2

型別switch守衛前面可以帶有一個簡單語句,該語句在守衛求值之前執行。

型別switch中不允許使用"fallthrough"語句。

For 語句

"for"語句指定一個程式碼塊的重複執行。有三種形式:迭代可以透過單個條件、"for"子句或"range"子句來控制。

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

帶單個條件的For語句

在其最簡單的形式中,"for"語句指定只要布林條件求值為真,就重複執行一個程式碼塊。條件在每次迭代之前求值。如果條件缺失,則等價於布林值true

for a < b {
	a *= 2
}

for子句的For語句

帶ForClause的"for"語句也受其條件控制,但它還可以指定一個initpost語句,例如賦值、增量或減量語句。init語句可以是短變數宣告,但post語句不能。

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

如果非空,init語句在第一次迭代求值條件之前執行一次;post語句在每次執行程式碼塊之後(並且僅在程式碼塊執行之後)執行。ForClause的任何元素都可以為空,但分號是必需的,除非只有一個條件。如果條件缺失,則等價於布林值true

for cond { S() }    is the same as    for ; cond ; { S() }
for      { S() }    is the same as    for true     { S() }

每次迭代都有自己單獨宣告的變數(或變數集)[Go 1.22]。第一次迭代使用的變數由init語句宣告。每次後續迭代使用的變數在執行post語句之前隱式宣告,並初始化為前一次迭代變數在那個時刻的值。

var prints []func()
for i := 0; i < 5; i++ {
	prints = append(prints, func() { println(i) })
	i++
}
for _, p := range prints {
	p()
}

列印

1
3
5

在[Go 1.22]之前,迭代共享一組變數而不是擁有自己單獨的變數。在這種情況下,上面的例子列印

6
6
6

range子句的For語句

帶"range"子句的"for"語句遍歷陣列、切片、字串或對映的所有條目、通道上接收到的值、從零到上限的整數值[Go 1.22],或傳遞給迭代器函式yield函式的值[Go 1.23]。對於每個條目,它會將迭代值賦值給對應的迭代變數(如果存在),然後執行程式碼塊。

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

"range"子句中右側的表示式稱為range表示式,它可以是陣列、陣列指標、切片、字串、對映、允許接收操作的通道、整數或具有特定簽名的函式(見下文)。與賦值一樣,如果存在,左側的運算數必須是可定址的或對映索引表示式;它們表示迭代變數。如果range表示式是一個函式,則迭代變數的最大數量取決於函式簽名。如果range表示式是通道或整數,則最多允許一個迭代變數;否則最多允許兩個。如果最後一個迭代變數是空白識別符號,則range子句等價於沒有該識別符號的相同子句。

range表示式x在迴圈開始之前求值,但有一個例外:如果最多隻有一個迭代變數存在,並且xlen(x)常量,則range表示式不求值。

左側的函式呼叫每次迭代求值一次。對於每次迭代,如果存在相應的迭代變數,則按以下方式生成迭代值

Range expression                                       1st value                2nd value

array or slice      a  [n]E, *[n]E, or []E             index    i  int          a[i]       E
string              s  string type                     index    i  int          see below  rune
map                 m  map[K]V                         key      k  K            m[k]       V
channel             c  chan E, <-chan E                element  e  E
integer value       n  integer type, or untyped int    value    i  see below
function, 0 values  f  func(func() bool)
function, 1 value   f  func(func(V) bool)              value    v  V
function, 2 values  f  func(func(K, V) bool)           key      k  K            v          V
  1. 對於陣列、陣列指標或切片值a,索引迭代值以遞增順序生成,從元素索引0開始。如果最多隻有一個迭代變數存在,則range迴圈生成從0到len(a)-1的迭代值,並且不索引到陣列或切片本身。對於nil切片,迭代次數為0。
  2. 對於字串值,"range"子句迭代字串中從位元組索引0開始的Unicode碼點。在連續迭代中,索引值將是字串中連續UTF-8編碼碼點的第一個位元組的索引,第二個值,型別為rune,將是相應碼點的值。如果迭代遇到無效的UTF-8序列,第二個值將是0xFFFD,即Unicode替換字元,並且下一次迭代將在字串中前進一個位元組。
  3. 對映的迭代順序未指定,並且不保證從一次迭代到下一次迭代保持相同。如果在迭代期間刪除了尚未到達的對映條目,則不會生成相應的迭代值。如果在迭代期間建立了對映條目,則該條目可能在迭代期間生成,也可能被跳過。選擇可能因每個建立的條目和從一次迭代到下一次迭代而異。如果對映為nil,則迭代次數為0。
  4. 對於通道,生成的迭代值是通道上連續傳送的值,直到通道關閉。如果通道為nil,則range表示式會永遠阻塞。
  5. 對於整數值n,其中n整數型別或無型別整數常量,迭代值0到n-1以遞增順序生成。如果n是整數型別,則迭代值具有相同的型別。否則,n的型別被確定為好像它被賦值給迭代變數一樣。具體來說:如果迭代變數已存在,則迭代值的型別是迭代變數的型別,該型別必須是整數型別。否則,如果迭代變數由"range"子句宣告或缺失,則迭代值的型別是n預設型別。如果n <= 0,則迴圈不執行任何迭代。
  6. 對於函式f,迭代透過呼叫f並將其新的、合成的yield函式作為其引數進行。如果在f返回之前呼叫了yield,則yield的引數將成為執行迴圈體一次的迭代值。在每次連續的迴圈迭代之後,yield返回true並可以再次呼叫以繼續迴圈。只要迴圈體不終止,"range"子句將以這種方式為每次yield呼叫繼續生成迭代值,直到f返回。如果迴圈體終止(例如透過break語句),則yield返回false,並且不得再次呼叫。

迭代變數可以透過"range"子句使用短變數宣告:=)的形式宣告。在這種情況下,它們的作用域是"for"語句的程式碼塊,並且每次迭代都有自己新的變數[Go 1.22](另請參閱帶ForClause的"for"語句)。變數具有其各自迭代值的型別。

如果迭代變數沒有被"range"子句顯式宣告,則它們必須是預先存在的。在這種情況下,迭代值像賦值語句一樣賦值給相應的變數。

var testdata *struct {
	a *[7]int
}
for i, _ := range testdata.a {
	// testdata.a is never evaluated; len(testdata.a) is constant
	// i ranges from 0 to 6
	f(i)
}

var a [10]string
for i, s := range a {
	// type of i is int
	// type of s is string
	// s == a[i]
	g(i, s)
}

var key string
var val interface{}  // element type of m is assignable to val
m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
for key, val = range m {
	h(key, val)
}
// key == last map key encountered in iteration
// val == map[key]

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

// empty a channel
for range ch {}

// call f(0), f(1), ... f(9)
for i := range 10 {
	// type of i is int (default type for untyped constant 10)
	f(i)
}

// invalid: 256 cannot be assigned to uint8
var u uint8
for u = range 256 {
}

// invalid: 1e3 is a floating-point constant
for range 1e3 {
}

// fibo generates the Fibonacci sequence
fibo := func(yield func(x int) bool) {
	f0, f1 := 0, 1
	for yield(f0) {
		f0, f1 = f1, f0+f1
	}
}

// print the Fibonacci numbers below 1000:
for x := range fibo {
	if x >= 1000 {
		break
	}
	fmt.Printf("%d ", x)
}
// output: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

// iteration support for a recursive tree data structure
type Tree[K cmp.Ordered, V any] struct {
	left, right *Tree[K, V]
	key         K
	value       V
}

func (t *Tree[K, V]) walk(yield func(key K, val V) bool) bool {
	return t == nil || t.left.walk(yield) && yield(t.key, t.value) && t.right.walk(yield)
}

func (t *Tree[K, V]) Walk(yield func(key K, val V) bool) {
	t.walk(yield)
}

// walk tree t in-order
var t Tree[string, int]
for k, v := range t.Walk {
	// process k, v
}

如果range表示式的型別是型別引數,其型別集中的所有型別都必須具有相同的底層型別,並且range表示式必須對該型別有效,或者,如果型別集包含通道型別,則它只能包含具有相同元素型別的通道型別,並且所有通道方向都不得衝突。

Go 語句

"go"語句將函式呼叫作為獨立的併發控制執行緒(或goroutine)在同一地址空間內開始執行。

GoStmt = "go" Expression .

表示式必須是函式或方法呼叫;它不能加括號。內建函式的呼叫受表示式語句的限制。

函式值和引數在呼叫goroutine中照常求值,但與常規呼叫不同,程式執行不會等待被呼叫的函式完成。相反,函式在一個新的goroutine中獨立開始執行。當函式終止時,其goroutine也終止。如果函式有任何返回值,它們在函式完成時被丟棄。

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

Select 語句

"select"語句選擇一組可能的傳送接收操作中的哪一個將繼續進行。它看起來類似於"switch"語句,但其case都引用通訊操作。

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

帶RecvStmt的case可以將RecvExpr的結果賦值給一個或兩個變數,這些變數可以使用短變數宣告來宣告。RecvExpr必須是(可能加括號的)接收操作。最多隻能有一個default case,並且它可以出現在case列表中的任何位置。

"select"語句的執行分幾個步驟進行

  1. 對於語句中的所有case,在進入"select"語句時,接收操作的通道運算數以及傳送語句的通道和右側表示式都會按原始碼順序精確求值一次。結果是一組用於接收或傳送的通道,以及相應的要傳送的值。無論是否選擇任何通訊操作進行,該求值中的任何副作用都將發生。帶有短變數宣告或賦值的RecvStmt左側的表示式尚未求值。
  2. 如果一個或多個通訊可以進行,則透過統一的偽隨機選擇選擇一個可以進行的通訊。否則,如果存在default case,則選擇該case。如果沒有default case,則"select"語句會阻塞,直到至少一個通訊可以進行。
  3. 除非選定的case是default case,否則執行相應的通訊操作。
  4. 如果選定的case是帶有短變數宣告或賦值的RecvStmt,則左側表示式會被求值並賦值收到的值(或多個值)。
  5. 執行選定case的語句列表。

由於nil通道上的通訊永遠無法進行,因此只包含nil通道且沒有default case的select會永遠阻塞。

var a []int
var c, c1, c2, c3, c4 chan int
var i1, i2 int
select {
case i1 = <-c1:
	print("received ", i1, " from c1\n")
case c2 <- i2:
	print("sent ", i2, " to c2\n")
case i3, ok := (<-c3):  // same as: i3, ok := <-c3
	if ok {
		print("received ", i3, " from c3\n")
	} else {
		print("c3 is closed\n")
	}
case a[f()] = <-c4:
	// same as:
	// case t := <-c4
	//	a[f()] = t
default:
	print("no communication\n")
}

for {  // send random sequence of bits to c
	select {
	case c <- 0:  // note: no statement, no fallthrough, no folding of cases
	case c <- 1:
	}
}

select {}  // block forever

Return 語句

函式F中的"return"語句終止F的執行,並可選擇提供一個或多個結果值。F延遲的任何函式在F返回其呼叫者之前執行。

ReturnStmt = "return" [ ExpressionList ] .

在沒有結果型別的函式中,"return"語句不能指定任何結果值。

func noResult() {
	return
}

從具有結果型別的函式返回值有三種方法

  1. 返回值或多個值可以在"return"語句中顯式列出。每個表示式必須是單值的,並且可以賦值給函式結果型別的相應元素。
    func simpleF() int {
    	return 2
    }
    
    func complexF1() (re float64, im float64) {
    	return -7.0, -4.0
    }
    
  2. "return"語句中的表示式列表可以是單個對多值函式的呼叫。其效果是,從該函式返回的每個值都好像被賦值給一個具有相應值型別的臨時變數,然後是一個列出這些變數的"return"語句,此時適用前一種情況的規則。
    func complexF2() (re float64, im float64) {
    	return complexF1()
    }
    
  3. 如果函式的結果型別為其結果引數指定了名稱,則表示式列表可以為空。結果引數充當普通的區域性變數,函式可以根據需要為其賦值。該"return"語句返回這些變數的值。
    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
    }
    

無論它們如何宣告,所有結果值在函式入口處都初始化為其型別的零值。指定結果的"return"語句在任何延遲函式執行之前設定結果引數。

實現限制:如果與結果引數同名的不同實體(常量、型別或變數)在返回位置作用域內,編譯器可能會禁止"return"語句中的空表示式列表。

func f(n int) (res int, err error) {
	if _, err := f(n-1); err != nil {
		return  // invalid return statement: err is shadowed
	}
	return
}

Break 語句

"break"語句終止同一函式內最內層的"for""switch""select"語句的執行。

BreakStmt = "break" [ Label ] .

如果存在標籤,它必須是封閉的"for"、"switch"或"select"語句的標籤,並且是終止其執行的語句。

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 語句

"continue"語句透過將控制流推進到迴圈塊的末尾,開始最內層封閉"for"迴圈的下一次迭代。"for"迴圈必須在同一函式內。

ContinueStmt = "continue" [ Label ] .

如果存在標籤,它必須是封閉的"for"語句的標籤,並且是其執行推進的語句。

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

Goto 語句

"goto"語句將控制流轉移到同一函式內具有相應標籤的語句。

GotoStmt = "goto" Label .
goto Error

執行"goto"語句不得導致任何變數進入作用域,這些變數在goto點處尚未在作用域內。例如,此示例

	goto L  // BAD
	v := 3
L:

是錯誤的,因為跳轉到標籤L跳過了v的建立。

塊外部的"goto"語句不能跳轉到該塊內部的標籤。例如,此示例

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

是錯誤的,因為標籤L1在"for"語句的塊內部,但goto不在。

Fallthrough 語句

"fallthrough"語句將控制流轉移到表示式"switch"語句中下一個case子句的第一個語句。它只能用作此類子句中最終的非空語句。

FallthroughStmt = "fallthrough" .

Defer 語句

"defer"語句呼叫一個函式,該函式的執行被推遲到周圍函式返回的時刻,原因可能是周圍函式執行了return語句,到達了其函式體的末尾,或者相應的goroutine正在panic

DeferStmt = "defer" Expression .

表示式必須是函式或方法呼叫;它不能加括號。內建函式的呼叫受表示式語句的限制。

每次"defer"語句執行時,函式值和呼叫的引數都會照常求值並重新儲存,但實際函式不會被呼叫。相反,延遲函式在周圍函式返回之前立即被呼叫,順序與它們被延遲的順序相反。也就是說,如果周圍函式透過顯式return語句返回,延遲函式會在該return語句設定任何結果引數之後執行,但在函式返回其呼叫者之前執行。如果延遲函式值求值為nil,則在呼叫函式時(而不是在執行"defer"語句時)會發生panic

例如,如果延遲函式是函式字面量,並且周圍函式具有在字面量作用域內的命名結果引數,則延遲函式可以在結果引數返回之前訪問和修改它們。如果延遲函式有任何返回值,它們在函式完成時被丟棄。(另請參閱處理panic一節。)

lock(l)
defer unlock(l)  // unlocking happens before surrounding function returns

// prints 3 2 1 0 before surrounding function returns
for i := 0; i <= 3; i++ {
	defer fmt.Print(i)
}

// f returns 42
func f() (result int) {
	defer func() {
		// result is accessed after it was set to 6 by the return statement
		result *= 7
	}()
	return 6
}

內建函式

內建函式是預宣告的。它們像任何其他函式一樣被呼叫,但其中一些接受型別而不是表示式作為第一個引數。

內建函式沒有標準Go型別,因此它們只能出現在呼叫表示式中;它們不能用作函式值。

切片的追加和複製

內建函式appendcopy輔助常見的切片操作。對於這兩個函式,結果與引數引用的記憶體是否重疊無關。

可變引數函式append將零個或多個值x追加到型別為S的切片s,並返回結果切片,也為型別S。值x傳遞給型別為...E的引數,其中ES的元素型別,並應用相應的引數傳遞規則。作為特例,append也接受一個可賦值給[]byte型別的第一引數,以及一個字串型別的第二引數,後跟...。這種形式會追加字串的位元組。

append(s S, x ...E) S  // E is the element type of S

如果S型別引數,則其型別集中的所有型別都必須具有相同的底層切片型別[]E

如果s的容量不足以容納附加值,append分配一個新的、足夠大的底層陣列,以容納現有切片元素和附加值。否則,append會重用底層陣列。

s0 := []int{0, 0}
s1 := append(s0, 2)                // append a single element     s1 is []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)          // append multiple elements    s2 is []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)            // append a slice              s3 is []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...)   // append overlapping slice    s4 is []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

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

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

函式copy將切片元素從源src複製到目標dst並返回複製的元素數量。兩個引數必須具有相同的元素型別E,並且必須可以賦值給型別為[]E的切片。複製的元素數量是len(src)len(dst)中的最小值。作為特例,copy也接受一個可賦值給[]byte型別的目標引數,以及一個string型別的源引數。這種形式將字串中的位元組複製到位元組切片中。

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

如果一個或兩個引數的型別是型別引數,則它們各自型別集中的所有型別都必須具有相同的底層切片型別[]E

示例

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 is []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:])            // n2 == 4, s is []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "Hello, World!")  // n3 == 5, b is []byte("Hello")

清空

內建函式clear接受對映切片型別引數型別引數,並刪除或清空所有元素[Go 1.21]。

Call        Argument type     Result

clear(m)    map[K]T           deletes all entries, resulting in an
                              empty map (len(m) == 0)

clear(s)    []T               sets all elements up to the length of
                              s to the zero value of T

clear(t)    type parameter    see below

如果clear的引數型別是型別引數,則其型別集中的所有型別都必須是對映或切片,並且clear會執行與實際型別引數對應的操作。

如果對映或切片為nilclear是空操作。

關閉

對於通道ch,內建函式close(ch)記錄該通道不再發送值。如果ch是隻接收通道,則會報錯。向已關閉通道傳送或關閉已關閉通道會導致執行時panic。關閉nil通道也會導致執行時panic。呼叫close後,並且在接收完之前傳送的任何值之後,接收操作將返回通道型別的零值,而不會阻塞。多值接收操作返回一個接收到的值以及一個通道是否已關閉的指示。

如果close的引數型別是型別引數,則其型別集中的所有型別都必須是具有相同元素型別的通道。如果其中任何通道是隻接收通道,則會報錯。

操作複數

有三個函式用於組裝和拆解複數。內建函式complex從浮點實部和虛部構造一個複數值,而realimag提取複數值的實部和虛部。

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

引數和返回值的型別對應。對於complex,兩個引數必須是相同的浮點型別,返回型別是具有相應浮點成分的複數型別complex64對應float32引數,complex128對應float64引數。如果其中一個引數求值為無型別常量,它會首先被隱式轉換為另一個引數的型別。如果兩個引數都求值為無型別常量,它們必須是非複數或其虛部必須為零,並且函式的返回值為無型別複數常量。

對於realimag,引數必須是複數型別,返回型別是相應的浮點型別:complex64引數對應float32complex128引數對應float64。如果引數求值為無型別常量,它必須是一個數字,並且函式的返回值為無型別浮點常量。

realimag函式一起構成complex的逆操作,因此對於複數型別Z的值zz == Z(complex(real(z), imag(z)))

如果這些函式的所有運算數都是常量,則返回值為常量。

var a = complex(2, -2)             // complex128
const b = complex(1.0, -1.4)       // untyped complex constant 1 - 1.4i
x := float32(math.Cos(math.Pi/2))  // float32
var c64 = complex(5, -x)           // complex64
var s int = complex(1, 0)          // untyped complex constant 1 + 0i can be converted to int
_ = complex(1, 2<<s)               // illegal: 2 assumes floating-point type, cannot shift
var rl = real(c64)                 // float32
var im = imag(a)                   // float64
const c = imag(b)                  // untyped constant -1.4
_ = imag(3 << s)                   // illegal: 3 assumes complex type, cannot shift

不允許使用型別引數型別的引數。

刪除對映元素

內建函式delete對映m中刪除鍵為k的元素。值k必須可以賦值m的鍵型別。

delete(m, k)  // remove element m[k] from map m

如果m的型別是型別引數,則該型別集中的所有型別都必須是對映,並且它們必須都具有相同的鍵型別。

如果對映mnil或元素m[k]不存在,則delete是空操作。

長度和容量

內建函式lencap接受各種型別的引數並返回int型別的結果。實現保證結果總是適合int型別。

Call      Argument type    Result

len(s)    string type      string length in bytes
          [n]T, *[n]T      array length (== n)
          []T              slice length
          map[K]T          map length (number of defined keys)
          chan T           number of elements queued in channel buffer
          type parameter   see below

cap(s)    [n]T, *[n]T      array length (== n)
          []T              slice capacity
          chan T           channel buffer capacity
          type parameter   see below

如果引數型別是型別引數P,則呼叫len(e)(或cap(e))必須對P的型別集中的每種型別都有效。結果是其型別對應於例項化P的型別引數的引數的長度(或容量)。

切片的容量是在底層陣列中分配空間的元素數量。在任何時候都存在以下關係

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

nil切片、對映或通道的長度為0。nil切片或通道的容量為0。

如果s是字串常量,則表示式len(s)常量。如果s的型別是陣列或陣列指標,並且表示式s不包含通道接收或(非常量)函式呼叫,則表示式len(s)cap(s)是常量;在這種情況下,s不求值。否則,lencap的呼叫不是常量,並且s被求值。

const (
	c1 = imag(2i)                    // imag(2i) = 2.0 is a constant
	c2 = len([10]float64{2})         // [10]float64{2} contains no function calls
	c3 = len([10]float64{c1})        // [10]float64{c1} contains no function calls
	c4 = len([10]float64{imag(2i)})  // imag(2i) is a constant and no function call is issued
	c5 = len([10]float64{imag(z)})   // invalid: imag(z) is a (non-constant) function call
)
var z complex128

建立切片、對映和通道

內建函式make接受一個型別T(必須是切片、對映或通道型別,或型別引數),可選後跟型別特定的表示式列表。它返回一個型別為T的值(而不是*T)。記憶體按照初始值一節中的描述進行初始化。

Call             Type T            Result

make(T, n)       slice             slice of type T with length n and capacity n
make(T, n, m)    slice             slice of type T with length n and capacity m

make(T)          map               map of type T
make(T, n)       map               map of type T with initial space for approximately n elements

make(T)          channel           unbuffered channel of type T
make(T, n)       channel           buffered channel of type T, buffer size n

make(T, n)       type parameter    see below
make(T, n, m)    type parameter    see below

如果第一個引數是型別引數,則其型別集中的所有型別都必須具有相同的底層型別,該型別必須是切片或對映型別;或者,如果存在通道型別,則必須只存在通道型別,它們必須都具有相同的元素型別,並且通道方向不得衝突。

大小引數nm都必須是整數型別,具有隻包含整數型別的型別集,或者是無型別常量。常量大小引數必須是非負的,並且可以由int型別的值表示;如果它是無型別常量,則賦予型別int。如果nm都提供並且是常量,則n不能大於m。對於切片和通道,如果在執行時n為負數或大於m,則會發生執行時panic

s := make([]int, 10, 100)       // slice with len(s) == 10, cap(s) == 100
s := make([]int, 1e3)           // slice with len(s) == cap(s) == 1000
s := make([]int, 1<<63)         // illegal: len(s) is not representable by a value of type int
s := make([]int, 10, 0)         // illegal: len(s) > cap(s)
c := make(chan int, 10)         // channel with a buffer size of 10
m := make(map[string]int, 100)  // map with initial space for approximately 100 elements

使用對映型別和大小提示n呼叫make將建立一個具有初始空間以容納n個對映元素的對映。具體行為是實現相關的。

Min 和 Max

內建函式minmax計算固定數量的有序型別引數中的最小值或最大值。必須至少有一個引數[Go 1.21]。

運算子相同的型別規則適用:對於有序引數xy,如果x + y有效,則min(x, y)有效,並且min(x, y)的型別是x + y的型別(max也類似)。如果所有引數都是常量,則結果是常量。

var x, y int
m := min(x)                 // m == x
m := min(x, y)              // m is the smaller of x and y
m := max(x, y, 10)          // m is the larger of x and y but at least 10
c := max(1, 2.0, 10)        // c == 10.0 (floating-point kind)
f := max(0, float32(x))     // type of f is float32
var s []string
_ = min(s...)               // invalid: slice arguments are not permitted
t := max("", "foo", "bar")  // t == "foo" (string kind)

對於數值引數,假設所有NaN都相等,minmax是可交換和結合的

min(x, y)    == min(y, x)
min(x, y, z) == min(min(x, y), z) == min(x, min(y, z))

對於浮點引數負零、NaN和無窮大,適用以下規則

   x        y    min(x, y)    max(x, y)

  -0.0    0.0         -0.0          0.0    // negative zero is smaller than (non-negative) zero
  -Inf      y         -Inf            y    // negative infinity is smaller than any other number
  +Inf      y            y         +Inf    // positive infinity is larger than any other number
   NaN      y          NaN          NaN    // if any argument is a NaN, the result is a NaN

對於字串引數,min的結果是按位元組字典比較值最小的第一個引數(或max,最大的)

min(x, y)    == if x <= y then x else y
min(x, y, z) == min(min(x, y), z)

分配

內建函式new接受一個型別T,在執行時為該型別的變數分配儲存空間,並返回一個型別為*T的值,指向該變數。變數按照初始值一節中的描述進行初始化。

new(T)

例如

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

為型別S的變數分配儲存空間,初始化它(a=0b=0.0),並返回一個型別為*S的值,其中包含該位置的地址。

處理panic

兩個內建函式panicrecover輔助報告和處理執行時panic和程式定義的錯誤條件。

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

在執行函式F時,對panic的顯式呼叫或執行時panic會終止F的執行。F延遲的任何函式會照常執行。接下來,F的呼叫者執行的任何延遲函式會執行,依此類推,直到執行goroutine中的頂層函式延遲的任何函式。此時,程式終止並報告錯誤條件,包括panic引數的值。這種終止序列稱為panicking

panic(42)
panic("unreachable")
panic(Error("cannot parse"))

recover函式允許程式管理panicking goroutine的行為。假設函式G延遲了一個呼叫recover的函式D,並且在與G正在執行的同一個goroutine中的函式中發生了panic。當延遲函式的執行到達D時,Drecover的返回值將是傳遞給panic呼叫的值。如果D正常返回,而沒有啟動新的panic,則panicking序列停止。在這種情況下,Gpanic呼叫之間呼叫的函式的狀態被丟棄,正常執行恢復。GD之前延遲的任何函式然後執行,並且G的執行透過返回其呼叫者而終止。

當goroutine沒有panicking或者recover不是由延遲函式直接呼叫時,recover的返回值為nil。相反,如果goroutine正在panicking並且recover是由延遲函式直接呼叫,則recover的返回值保證不是nil。為確保這一點,使用nil介面值(或無型別nil)呼叫panic會導致執行時panic

以下示例中的protect函式呼叫函式引數g並保護呼叫者免受g引起的執行時panic。

func protect(g func()) {
	defer func() {
		log.Println("done")  // Println executes normally even if there is a panic
		if x := recover(); x != nil {
			log.Printf("run time panic: %v", x)
		}
	}()
	log.Println("start")
	g()
}

引導

當前的實現提供了幾個在引導期間有用的內建函式。這些函式是為了完整性而記錄的,但不保證會保留在語言中。它們不返回結果。

Function   Behavior

print      prints all arguments; formatting of arguments is implementation-specific
println    like print but prints spaces between arguments and a newline at the end

實現限制:printprintln不需要接受任意引數型別,但必須支援布林、數字和字串型別的列印。

軟體包

Go程式透過將連結在一起構建。包又由一個或多個原始檔構成,這些原始檔共同宣告屬於該包的常量、型別、變數和函式,並且在同一包的所有檔案中都可以訪問。這些元素可以被匯出並在另一個包中使用。

原始檔組織

每個原始檔都包含一個包子句,定義它所屬的包,然後是一組可能為空的匯入宣告,宣告它希望使用的包的內容,然後是一組可能為空的函式、型別、變數和常量宣告。

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

包子句

包子句開始每個原始檔並定義檔案所屬的包。

PackageClause = "package" PackageName .
PackageName   = identifier .

PackageName不能是空白識別符號

package math

共享相同PackageName的一組檔案構成一個包的實現。實現可能要求包的所有原始檔都在同一個目錄中。

匯入宣告

匯入宣告表明包含宣告的原始檔依賴於匯入包的功能(§程式初始化和執行),並允許訪問該包的匯出識別符號。匯入指定一個用於訪問的識別符號(PackageName)和一個指定要匯入的包的ImportPath。

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

PackageName用於限定識別符號,以在匯入原始檔中訪問包的匯出識別符號。它在檔案塊中宣告。如果省略PackageName,它將預設為匯入包的包子句中指定的識別符號。如果顯式句點(.)代替名稱出現,則該包的包塊中宣告的所有匯出識別符號都將在匯入原始檔的檔案塊中宣告,並且必須不帶限定符訪問。

ImportPath的解釋是實現相關的,但它通常是編譯包的完整檔名的子字串,並且可能相對於已安裝包的倉庫。

實現限制:編譯器可能將ImportPath限制為非空字串,僅使用Unicode的L、M、N、P和S通用類別(沒有空格的圖形字元)中的字元,並且還可能排除字元!"#$%&'()*,:;<=>?[\]^`{|}和Unicode替換字元U+FFFD。

考慮一個包含包子句package math的已編譯包,該包匯出函式Sin,並將已編譯包安裝在由"lib/math"標識的檔案中。下表說明了在各種型別的匯入宣告之後,檔案中如何訪問Sin

Import declaration          Local name of Sin

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

匯入宣告聲明瞭匯入包和被匯入包之間的依賴關係。包直接或間接匯入自身,或直接匯入一個包而不引用其任何匯出識別符號都是非法的。為了僅出於其副作用(初始化)而匯入一個包,請使用空白識別符號作為顯式包名

import _ "lib/math"

一個示例包

這是一個完整的Go包,實現了併發素數篩。

package main

import "fmt"

// Send the sequence 2, 3, 4, … to channel 'ch'.
func generate(ch chan<- int) {
	for i := 2; ; i++ {
		ch <- i  // Send 'i' to channel 'ch'.
	}
}

// Copy the values from channel 'src' to channel 'dst',
// removing those divisible by 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
	for i := range src {  // Loop over values received from 'src'.
		if i%prime != 0 {
			dst <- i  // Send 'i' to channel 'dst'.
		}
	}
}

// The prime sieve: Daisy-chain filter processes together.
func sieve() {
	ch := make(chan int)  // Create a new channel.
	go generate(ch)       // Start generate() as a subprocess.
	for {
		prime := <-ch
		fmt.Print(prime, "\n")
		ch1 := make(chan int)
		go filter(ch, ch1, prime)
		ch = ch1
	}
}

func main() {
	sieve()
}

程式初始化和執行

零值

當透過宣告或呼叫new變數分配儲存空間時,或者當透過複合字面量或呼叫make建立新值時,如果沒有提供顯式初始化,變數或值將被賦予預設值。此類變數或值的每個元素都設定為其型別的零值:布林值為false,數字型別為0,字串為"",指標、函式、介面、切片、通道和對映為nil。這種初始化是遞迴進行的,因此例如,如果未指定值,結構體陣列的每個元素的欄位都將歸零。

這兩個簡單的宣告是等價的

var i int
var i int = 0

之後

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

以下成立

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

以下情況也同樣適用

var t T

包初始化

在一個包內,包級變數的初始化按步驟進行,每一步選擇宣告順序中最早且不依賴於未初始化變數的變數。

更準確地說,如果包級變數尚未初始化,並且沒有初始化表示式,或者其初始化表示式不依賴於未初始化變數,則該變數被視為準備好初始化。初始化透過重複初始化宣告順序中最早且準備好初始化的下一個包級變數進行,直到沒有變數準備好初始化。

如果在此過程結束時仍有任何變數未初始化,則這些變數是一個或多個初始化迴圈的一部分,程式無效。

變數宣告左側由單個(多值)表示式初始化並在右側的多個變數一起初始化:如果左側的任何變數被初始化,所有這些變數在同一步驟中初始化。

var x = a
var a, b = f() // a and b are initialized together, before x is initialized

為了包初始化的目的,空白變數在宣告中被視為與任何其他變數一樣。

在多個檔案中宣告的變數的宣告順序由檔案呈現給編譯器的順序決定:在第一個檔案中宣告的變數在第二個檔案中宣告的任何變數之前宣告,依此類推。為了確保可重現的初始化行為,建議構建系統以詞法檔名順序將屬於同一包的多個檔案呈現給編譯器。

依賴分析不依賴於變數的實際值,只依賴於原始碼中對它們的詞法引用,進行傳遞分析。例如,如果變數x的初始化表示式引用了一個函式,該函式的函式體引用了變數y,那麼x依賴於y。具體來說

例如,給定宣告:

var (
	a = c + b  // == 9
	b = f()    // == 4
	c = f()    // == 5
	d = 3      // == 5 after initialization has finished
)

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

初始化順序是dbca。請注意,初始化表示式中子表示式的順序無關緊要:在此示例中,a = c + ba = b + c導致相同的初始化順序。

依賴分析是按包進行的;只考慮引用當前包中宣告的變數、函式和(非介面)方法的引用。如果變數之間存在其他隱藏資料依賴,則這些變數之間的初始化順序未指定。

例如,給定宣告

var x = I(T{}).ab()   // x has an undetected, hidden dependency on a and b
var _ = sideEffect()  // unrelated to x, a, or b
var a = b
var b = 42

type I interface      { ab() []int }
type T struct{}
func (T) ab() []int   { return []int{a, b} }

變數a將在b之後初始化,但x是在b之前初始化,還是在ba之間,還是在a之後初始化,以及因此sideEffect()被呼叫的時刻(在x初始化之前或之後)都是未指定的。

變數也可以使用在包塊中宣告的名為init的函式進行初始化,該函式不帶引數且不帶結果引數。

func init() { … }

每個包可以定義多個這樣的函式,甚至在單個原始檔中。在包塊中,init識別符號只能用於宣告init函式,但識別符號本身未宣告。因此,程式中的任何位置都不能引用init函式。

整個包透過將初始值賦值給所有包級變數,然後按照它們在原始碼中(可能在多個檔案中)呈現給編譯器的順序呼叫所有init函式來初始化。

程式初始化

一個完整程式的包是分步初始化的,每次一個包。如果一個包有匯入,則匯入的包會在該包本身初始化之前初始化。如果多個包匯入一個包,則匯入的包只會被初始化一次。包的匯入,透過構造,保證不會出現迴圈初始化依賴。更準確地說

給定所有包的列表,按匯入路徑排序,每一步都會初始化列表中第一個所有匯入包(如果存在)都已初始化的未初始化包。此步驟重複進行,直到所有包都初始化完畢。

包初始化——變數初始化和init函式的呼叫——在一個goroutine中按順序進行,一次一個包。init函式可以啟動其他goroutine,它們可以與初始化程式碼併發執行。然而,初始化總是對init函式進行排序:它不會呼叫下一個函式,直到前一個函式返回。

程式執行

一個完整的程式是透過將一個未匯入的包(稱為main包)與其所有間接匯入的包連結起來建立的。main包必須具有包名main並宣告一個不帶引數且不返回值的函式main

func main() { … }

程式執行從初始化程式開始,然後呼叫main包中的函式main。當該函式呼叫返回時,程式退出。它不會等待其他(非main)goroutine完成。

Errors

預宣告型別error定義為

type error interface {
	Error() string
}

它是表示錯誤條件的傳統介面,nil值表示沒有錯誤。例如,從檔案讀取資料的函式可以定義為

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

執行時panic

執行錯誤,例如嘗試陣列越界索引,會觸發執行時panic,相當於呼叫內建函式panic,其值為實現定義的介面型別runtime.Error。該型別滿足預宣告的介面型別error。表示不同執行時錯誤條件的具體錯誤值未指定。

package runtime

type Error interface {
	error
	// and perhaps other methods
}

系統考量

unsafe

內建包unsafe,編譯器已知並透過匯入路徑"unsafe"訪問,提供低階程式設計設施,包括違反型別系統的操作。使用unsafe的包必須手動審查其型別安全,並且可能不可移植。該包提供以下介面

package unsafe

type ArbitraryType int  // shorthand for an arbitrary Go type; it is not a real type
type Pointer *ArbitraryType

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

type IntegerType int  // shorthand for an integer type; it is not a real type
func Add(ptr Pointer, len IntegerType) Pointer
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
func SliceData(slice []ArbitraryType) *ArbitraryType
func String(ptr *byte, len IntegerType) string
func StringData(str string) *byte

Pointer指標型別,但Pointer值不能被解引用。任何指標或底層型別uintptr的值都可以轉換為底層型別為Pointer的型別,反之亦然。如果相應的型別是型別引數,則它們各自型別集中的所有型別都必須具有相同的底層型別,分別為uintptrPointer。在Pointeruintptr之間轉換的效果是實現定義的。

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

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

func f[P ~*B, B any](p P) uintptr {
	return uintptr(unsafe.Pointer(p))
}

var p ptr = nil

函式AlignofSizeof接受任何型別的表示式x,並返回假設變數v的對齊或大小,就好像v是透過var v = x宣告的一樣。

函式Offsetof接受一個(可能加括號的)選擇器s.f,表示結構體s*s表示的結構體中的欄位f,並返回相對於結構體地址的欄位偏移量(以位元組為單位)。如果f嵌入欄位,則必須無需透過結構體欄位進行指標間接引用即可到達。對於具有欄位f的結構體s

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

計算機體系結構可能要求記憶體地址是對齊的;也就是說,變數的地址是某個因子(變數型別對齊)的倍數。函式Alignof接受表示任何型別變數的表示式,並返回該變數(型別)的對齊(以位元組為單位)。對於變數x

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

如果型別T型別引數,或者它是一個包含可變大小元素或欄位的陣列或結構體型別,則(型別為)T具有可變大小。否則,大小為常量。對AlignofOffsetofSizeof的呼叫是型別為uintptr的編譯時常量表達式,如果它們的引數(或Offsetof選擇器表示式s.f中的結構體s)是常量大小的型別。

函式Addlen新增到ptr並返回更新後的指標unsafe.Pointer(uintptr(ptr) + uintptr(len))[Go 1.17]。len引數必須是整數型別或無型別常量。常量len引數必須可以由int型別的值表示;如果它是無型別常量,則賦予型別intPointer的有效使用規則仍然適用。

函式Slice返回一個切片,其底層陣列從ptr開始,其長度和容量均為lenSlice(ptr, len)等價於

(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]

除了作為特例,如果ptrnillen為零,則Slice返回nil[Go 1.17]。

len引數必須是整數型別或無型別常量。常量len引數必須是非負的,並且可以由int型別的值表示;如果它是無型別常量,則賦予型別int。在執行時,如果len為負數,或者如果ptrnillen不為零,則會發生執行時panic[Go 1.17]。

函式 SliceData 返回 slice 引數底層陣列的指標。如果切片的容量 cap(slice) 不為零,該指標為 &slice[:1][0]。如果 slicenil,結果為 nil。否則它是一個非 nil 指標,指向一個未指定的記憶體地址 [Go 1.20]。

函式 String 返回一個 string 值,其底層位元組從 ptr 開始,長度為 lenptrlen 引數的要求與函式 Slice 相同。如果 len 為零,結果是空字串 ""。由於 Go 字串是不可變的,因此傳遞給 String 的位元組之後不得修改。 [Go 1.20]

函式 StringData 返回 str 引數底層位元組的指標。對於空字串,返回值未指定,可能為 nil。由於 Go 字串是不可變的,因此 StringData 返回的位元組不得修改 [Go 1.20]。

大小和對齊保證

對於數值型別,保證以下大小:

type                                 size in bytes

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

保證以下最小對齊屬性:

  1. 對於任何型別的變數 xunsafe.Alignof(x) 至少為 1。
  2. 對於結構體型別的變數 xunsafe.Alignof(x)x 的每個欄位 f 的所有 unsafe.Alignof(x.f) 值中最大的一個,但至少為 1。
  3. 對於陣列型別的變數 xunsafe.Alignof(x) 與陣列元素型別的變數的對齊方式相同。

如果一個結構體或陣列型別不包含大小大於零的欄位(或元素),則其大小為零。兩個不同的零大小變數可能具有相同的記憶體地址。

附錄

語言版本

Go 1 相容性保證 確保根據 Go 1 規範編寫的程式在該規範的生命週期內將繼續編譯和正確執行,無需更改。更廣泛地說,隨著語言的調整和功能的新增,相容性保證確保使用特定 Go 語言版本正常工作的 Go 程式將繼續與任何後續版本正常工作。

例如,使用字首 0b 表示二進位制整數文字的能力是在 Go 1.13 中引入的,在整數文字一節中用 [Go 1.13] 表示。如果編譯器使用隱式或要求的語言版本早於 Go 1.13,則包含整數文字(如 0b1011)的原始碼將被拒絕。

下表描述了 Go 1 之後引入的功能所需的最低語言版本。

Go 1.9

Go 1.13

Go 1.14

Go 1.17

Go 1.18

1.18 版本為語言添加了多型函式和型別(“泛型”)。具體來說

Go 1.20

Go 1.21

Go 1.22

Go 1.23

Go 1.24

型別統一規則

型別統一規則描述了兩種型別是否以及如何統一。精確的細節與 Go 實現相關,影響錯誤訊息的具體內容(例如編譯器是報告型別推斷錯誤還是其他錯誤),並可能解釋為什麼型別推斷在異常程式碼情況下失敗。但總的來說,在編寫 Go 程式碼時可以忽略這些規則:型別推斷旨在大部分“按預期工作”,統一規則也相應地進行了微調。

型別統一由一個匹配模式控制,該模式可以是精確寬鬆。隨著統一遞迴地下降到複合型別結構,用於型別元素的匹配模式,即元素匹配模式,與匹配模式保持相同,除非兩種型別為可賦值性 (A) 而統一:在這種情況下,匹配模式在頂層是寬鬆的,然後對於元素型別變為精確,反映了型別不必完全相同即可賦值的事實。

兩個非繫結型別引數的型別在滿足以下任何條件時精確統一:

如果兩種型別都是繫結型別引數,則它們根據給定的匹配模式統一,如果:

單個繫結型別引數 P 和另一個型別 T 根據給定的匹配模式統一,如果:

最後,兩個非繫結型別引數的型別寬鬆統一(並根據元素匹配模式),如果: