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 根据给定的匹配模式统一,如果:

最后,两个非绑定类型参数的类型宽松统一(并根据元素匹配模式),如果: