本文译自The Go Programming Language Specification

翻译仅为学习使用,不准确的地方还请谅解。

1、Introduction

本文是Go编程语言的参考手册(reference manual),如想了解更多的信息和文档,请参考golang.org

Go是一门通用目的型(general-purpose)语言,它的设计始终都是为了系统编程。它有强类型(strongly typed)、自带垃圾收集(garbage-collected)、显示的支持并发编程等特点。程序(programs)由包(packages)构成,这种性质可以有效的帮助依赖管理(management of dependencies)。

Go的语法非常紧凑(compact)和规范(regular)。它还自带一些自动化分析工具(对分析程序性能非常友好)。

2、Notation

Go的句法(syntax)使用了扩展巴科斯-瑙尔范式

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

产生式(production)是一种表达式(expression),由termoperator构成,按照下面的优先级:

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

通常用全小写(lower-case)的产生式(production)来标记lexical token。Non-terminal则使用驼峰形式(CamelCase)。Lexical token被双引号或者单引号包括(enclose)。

形如a … b表示从a到b到字符是可选的。注意 这个字符和 的区别,前者不能当作Go语言中的token。

3、Source code representation

源码是使用UTF-8编码的Unicode文本。文本(text)不是规范化(canonicalized)的,因此一个单独的accented code point与一个accentletter组成的charater不是一码事。一个单独的accented code point被当作两个code point。为了简便,本片文档将会使用不太准确的term character来类比源码文本中的Unicode code point

每个code point都是不同的;比方说,大写A和小写a是两个不同的字符(character)。

实现限制:为了与其他工具兼容,Go的编译器不允许在源码文本(source text)中出现NUL字符。

实现限制:为了与其他工具兼容,Go的编译器碰到源码文本(source text)中的第一个code pointbyte order mark时,编译器会选择忽略它。除此之外,byte order mark不允许出现在源码文本中的其他任何地方。

3.1、Characters

使用下面的术语来表示一个特定的Unicode字符类别:

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

The Unicode Standard 8.0的第4.5节中,定义了一个字符类别集合,Go把Lu, Ll, Lt, Lm, or Lo这些种类中的所有字符(character)都当作Unicode letter,把Nd种类下的都当作Unicode digit

3.2、Letters and digits

在Go中,下划线_被当作是一个字母(letter)。

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

4、Lexical elements

4.1、Comments

注释(comment)可以用来写程序文档,注释有下面两种形式:

rune、string literal和comment中不能包含注释。没有newline的注释表现得像一个空格。其他任何注释表现得像一个newline。

4.2、Tokens

Token组成了Go语言的词汇表(vocabulary)。有下面4个种类的token

后面会一一介绍。

White space可以由下面四种Unicode组成:

一般会忽略White space,除非它将一个token分离成了两个token。

通常来说一旦遇到newline或是EOF,会自动加上一个分号。

当把输入拆分成token时,下一个token是组成有效token当最长字符序列。

4.3、Semicolons

通常来说,在一系列production后接上一个分号标志着结束,在Go里面却没有采取这种语法,因为下面两条规则:

4.3.1、rule one

当输入被拆分成token,自动将一个分号插入到一行到最后一个token后面,这个最后的token必须是以下几种情况:

4.3.1、rule two

为了在一行中能够使用复杂等语句,) 或者 } 前的分号也可以省略

4.4、Identifiers

标识符(identifier)用来命名程序中的变量和类型。标识符由一个或者多个字母和数字组成,第一个字符必须是字母(译注:回忆前面说过,下划线 _ 也是字母,在后面会看到,这种标识符被称为blank identifier)。

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

有些标识符是预声明的(译注:留意后面Predeclared identifiers小节)。

4.5、Keywords

下面是Go语言中的关键字(keyword),它们不能用作标识符:

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

4.6、Operators and punctuation

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

(译注:这个列表中没有 =,实际上也是包括的)

4.7、Integer literals

整数字面量(integer literal)由一系列digit组成,它表示一个整数常量(integer constant)。整数字面量有一个可选的前缀:0b/0B表示二进制、0/0o/0O表示八进制、0x/0X表示十六进制。一个单0表示一个十进制0。

为了可读性,下划线 _ 可能出现在前缀之后或者连续的数字之间,这时字面量的值不受影响。

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  译注这里有个疑问下划线为什么能够出现在连续的字母之间TODO
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

4.8、Floating-point literals

浮点数字面量是十进制或者十六进制,用来表示一个浮点数常量。

4.8.1、decimal floating-point literal

一个十进制浮点数常量由下面几个部分组成:

integer或者fractional,可省略任意之一;小数点或者exponent可省略任意之一。

4.8.2、hexadecimal floating-point literal

一个十六进制浮点数常量由下面几个部分组成:

integer或者fractional,可省略任意之一;小数点是可选的,但是exponent部分是必须的。

为了可读性,下划线 _ 可能出现在前缀之后或者连续的数字之间,这时字面量的值不受影响。

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

4.9、Imaginary literals

虚数(imaginary)字面量表示复数常量(complex constant)的虚数部分。它由一个整数常量或浮点数常量、加上一个小写的字母 i 组成。这个虚数字面量的值为对应整数或者浮点数字面量的值乘以虚数单位 i。

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

为了向后兼容,只包含十进制数字(decimal digit)的虚数字面量的整数部分被当作十进制整数(decimal integer),甚至它们以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

4.10、Rune literals

符文字面量(rune literal)表示:(1)一个符文常量(rune constant)(2)一个确定了一个Unicode code point的整数值(ingeter value)。符文字面量由一个或者多个字符以单引号包裹来表示,就像 ‘x’ 或 ‘\n’ 。在单引号内,不允许出现newline和未转义的单引号(unescaped single quote)。只包含一个字符的符文字面量表示字符本身的Unicode值。以 \ 打头的字符序列则可以编码成不同形式的值。

The simplest form represents the single character within the quotes; since Go source text is Unicode characters encoded in UTF-8, multiple UTF-8-encoded bytes may represent a single integer value.

例如:

几个反斜杠转义允许任意值被编码为 ASCII 文本。有四种方式将一个整数值表示为一个numeric constant:

在这几种情况下,字面量的值等于数字在响应的base下的值。

尽管这些最终都表示一个整数,但是它们的有效范围不同。八进制转义必须表示 0 到 255 之间的值。十六进制转义满足条件的要求会因为构造不同而不同。\u 和 \U 代表了 Unicode 码位,所以在这里面有一些值是非法的,尤其是那些超过 0x10FFFF 的和代理了一半的(译注:查阅「 UTF-16 代理对」进行深入阅读)。

一个反斜杠加上一个单字符表示特殊的值:

\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'       // \x 后紧跟着两个十六进制数字
'\xff'
'\u12e4'     // \u 后紧跟着四个十六进制数字
'\U00101234' // \U 后紧跟着八个十六进制数字
'\''         // rune literal containing single quote character
'aa'         // illegal: too many characters
'\xa'        // illegal: too few hexadecimal digits
'\0'         // illegal: too few octal digits
'\uDFFF'     // illegal: surrogate half(???TODO)
'\U00110000' // illegal: invalid Unicode code point(???TODO)

4.11、String literals

一个字符串字面量(string literal)表示一个字符串常量(string constant),由一串字符组成。有两种形式:

4.11.1、raw string literals

原生字符串常量(raw string literal)在反引号内。在反引号内,不允许出现反引号。原生字符串常量的值有反引号内的字符组成的字符串构成。反斜杠没有特殊意义,并且可以包含newline。原生字符串字面量中的回车字符(’\r’)会从原始字符串值中所丢弃。

4.11.2、interpreted string literals

解释型字符串字面量(raw string literal)在双引号内。在双引号内,不允许出现newline和未转义的双引号。双引号内的文本组成了字面量的值,反斜杠转义以及限制都和符文字面量一样(不同的是,在解释型字符串字面值中,' 是非法的," 是合法的)。三个数字的的八进制(\nnn)和两个数字的十六进制(\xnn)表示独立的字节。因此字符串字面量 \377 和 \xFF 表示单个字节,值为0xFF=255。ÿ, \u00FF, \U000000FF 和 \xc3\xbf表示两个字节0xc3 0xbf,它是字符U+00FF的UTF-8编码。

5、Constants

常量(constant)有以下几种(其中*标记的又称数值(numeric)常量):

常量值通常表示如下:

布尔值是预声明常量(predeclared constant):truefalse。预声明标识符iota表示一个整数常量。

通常来说,复数常量是常量表达式(constant expression)的一种形式。

数值常量(numeric constant)能表示任意精度的值且不溢出。因此,没有常量能表示IEEE-754标准中的负零、无穷、not-a-number value。

常量可以有类型(typed),也可以无类型(untyped)。以下几种情况都是无类型的:

一个常量的类型可以通过常量声明(constant declaration)或常量转换(constant conversion)显示给出,也可以通过变量声明(variable declaration)或赋值(assignment)或表达式中的操作数(operand in expression)隐式给出。

无类型常量有一个默认类型。当上下文中需要一个有类型值时,无类型常量会被隐式的转换为该默认类型。例如,变量声明i := 0没有显示类型。下面是几种典型的默认类型:

实现限制:尽管数值常量(numeric constant)在Go语言中能表示任意精度,但是Go编译器在内部实现时对精度进行了限制。也就是说,每种实现都必须:

这些要求也适用于字面量常量,以及常量表达式的求值结果。

6、Variables

A variable is a storage location for holding a value.

变量允许的值的范围由变量类型决定。

变量声明、函数的形参和返回值、函数声明的签名、函数字面量等,这些有名变量(named variable)都会开辟一块空间(reserve storage)。

调用内置函数new()或者对一个复合字面量(composite literal)取地址都会在运行时分配空间。这种匿名变量(anonymous variable)通过指针间接引用。

结构变量(structured variable)如array、slice、struct包含元素(element)或域(field),可单独寻址。每个element/field都表现得像个变量。

变量的类型(静态类型)在声明变量时给出,如new()函数参数给出、复合字面量给出、结构变量的域给出。接口类型的变量有一个动态类型,这个类型是在运行时赋值给接口变量的一种具体类型(concrete type)。The dynamic type may vary during execution but values stored in interface variables are always assignable to the static type of the variable.

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

一个变量值通常在表达式中使用变量时取得,值为最近赋值给该变量的值。如果一个变量没有赋值,那么它的变量值就是其对应类型的零值(zero value)。

7、Types

A type determines a set of values together with operations and methods specific to those values.

类型由类型名(type name)或者类型字面量(type literal:which composes a type from existing types)表示。

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

Go语言本身预声明了一些确定的类型名。其余的类型则通过类型声明(type declaration)引入。复合类型如:array、struct、pointer、function、interface、slace、map、channel都可以使用类型字面量(type literal)构造。

每种类型T都有一个潜在类型(underlying type),如果T是预声明的boolean、numeric、string、type literal这几种类型之一,那么T的潜在类型就是T本身,其他情况下,其潜在类型就是在类型声明时T指定的那个类型的潜在类型。

type (
	A1 = string
	A2 = A1
)
type (
	B1 string
	B2 B1
	B3 []B1
	B4 B3
)

string、A1、A2、B1、B2这些类型的潜在类型都是string。[]B1、B3、B4这些类型的潜在类型都是 []B1。

7.1、Method sets

一个类型通常有一个方法集(method set)与之关联。接口类型的方法集就是它的接口。除此之外,任何其它类型T的方法集由以类型T为接收者所声明的所有方法组成。对应指针类型 *T 的方法集是以 *T 或 T 为接收者所声明的所有方法的集合(也就是说,它包含了 T 的方法集)。(译注:意思就是如果一个方法以T为接受者,那么这个方法既属于T,又属于 *T;如果一个方法以 *T 为接受者,那么这个方法只属于 *T)

截屏2019-10-27下午7.42.54.png

对于包含嵌入域(embedded field)的结构体的方法集会在后面讨论。其他任意类型都有一个空的方法集。在一个方法集内,每个方法都必须有一个唯一的非空的方法名。

一个类型的方法集决定了(1)这个类型实现的接口和(2)这个类型可以调用的方法。

7.2、Boolean types

一个布尔类型使用预声明常量true和false来表示布尔真值集合。预声明的布尔类型是bool,这是一个已定义类型(defined type)。

7.3、Numeric types

一个数值类型表示整数或浮点数值的集合。预声明且与架构无关的数值类型有下面这些:

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,所有的数值类型都是完全不同的已定义类型(defined type)。在不同数值类型间使用表达式或者赋值时,需要显示转换(explicit conversion)。例如,在一个特定架构上,int32和int可能有相同的大小,但是它们的类型却不同。

7.4、String types

一个字符串类型表示字符串值的集合。一个字符串的值是一个字节序列(可能为空)。字节数量称为字符串的长度,它永远是非负的。字符串是不可变的(immutable),一旦创建,其内容就不可能改变。预声明的字符串类型是string,这是一个已定义类型(defined type)。

字符串的长度可以用内置函数len()求得。如果字符串是一个常量,那么它的长度就是一个编译期常量(compile-time constant)。字符串的字节可以通过索引范围0~len(s)-1来获取。s[i]可以得到字符串的第i字节,但是&s[i]是非法的。

7.5、Array types

一个数组是同种类型且带有顺序的元素序列。元素(element)的数量称作数组的长度,非负。

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

长度也是数组类型的一部分。数组长度可以使用内置函数len()来获取。数组里的元素可以通过下标0~len(a)-1来寻址(译注:回忆上一节中,string中的字节求地址&s[i]是非法的)。数组通常是一维的,但是可以复合成多维。

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

7.6、Slice types

slice是一种描述符(descriptor),用来描述一个数组的连续片段(contiguous segment),然后提供对这个连续片段的访问能力。slice的类型是元素类型的数组的切片的集合。元素的个数称为slice的长度,非负。未初始化的slice的值为nil。

SliceType = "[" "]" ElementType .

slice的长度可以使用内置函数len()来获取。与数组不同的是,slice的长度可能会在执行过程中变化。slice里的元素可以通过下标0~len(s)-1来寻址。某一元素在silce中的索引可能小于这个元素在其底层数组中的索引。

slice一旦初始化,就总是与其底层数组相关联。同一数组之上的不同slice共享这个数组的存储空间,不同数组总是代表不同的存储空间。

slice的底层数组长度可以超过slice的末端。容量(capacity)是其超过的最大限度:容量是slice长度与数组超过slice的长度之和。slice的容量可以使用内置函数len()来获取。

一个新的、已经初始化的slice可以通过下面的方式创建:

make([]T, length, capacity)

下面两条语句是等价的:

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

和数组一样,slice通常是一维的,但是可以复合成多维。数组外套一层数组时,里层数组的长度通常是一样的(译注:可以理解为二维数组是“方形”)。可是,slice外套一层slice时,里层slice的长度却可以不同。而且,里层slice必须单独初始化。

7.7、Struct types

一个结构体(struct)是一些命名元素(named element,又称域(field))的序列,每一个命名元素都有一个名字(name)和一个类型(type)。域的名字可以通过IdentifierList显示指定或者EmbeddedField隐式指定。在一个结构体内,非空白域的名字必须唯一。

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

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

如果一个域指定了类型,但是没有显示指定域的名字,那么这种域被称为内嵌域(embedded field)。内嵌域必须指定类型名T、或者指向非接口类型的指针类型名*T(注意T本身不能是指针类型)。这种非限定(unqualified)的类型名被用作域的名字。

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

(译注:上面结构体中,域的名字都是T,结构体内产生了冲突)

对于一个结构体x

type x struct {
    T
}

(译注:照原文的说法,假设内嵌域T有一个域f或者方法f)

如果x.f是一个合法的选择器(selector,参考本文selector章节),那么x.f就是可提升的(promoted)。

可提升的域表现得像一个普通域,除了它们不能在结构体的复合字面量(composite literal)中以域的名字的方式使用。

给定一个结构体类型S和一个已定义类型T,可提升的方法包含在结构体S的方法集中,分为下面两种情况:

截屏2019-10-27下午8.00.53.png

一个域声明可以跟随者一个可选的字符串字面量标签(tag),它将会成为以后所有对应域声明的属性。一个空标签字符串等于没有标签。标签只有在两种情况下是可见的:(1)反射接口时(2)参与结构体的类型一致性(type identity,译注:关于什么是type identity可参考相应章节)时。

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"`
}

7.8、Pointer types

一个指针类型是指向一个给定类型的变量的所有指针的集合,称为指针的基本类型。未初始化指针的值为nil

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

7.9、Function types

一个函数类型是所有有着相同参数和返回类型的函数的集合。函数类型的未初始化变量的值为nil

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

对于函数类型中的参数或者返回值的名字(name),要么全有,要么全无。如果有,必须唯一(译注:为了简便,此处翻译与原文略有不同)。参数和返回值列表必须分别用圆括号包围(如果只有一个返回值,则不必要用圆括号)。

函数签名中最后一个参数类型前可以加上 前缀,这种参数称为变参(variadic)。调用时,可以为这个变参指定多个实参(argument)。

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)

7.10、Interface types

接口类型指定了方法集,称作接口。只要一个类型T的方法集是接口的超集,这个接口类型的变量就能存储这个类型T的值。这样的类型,称为实现(implement)了这个接口。接口类型的未初始化变量的值为nil

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

接口类型的方法集里的方法名必须唯一,且非空白名。

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

(译注:注意上面的书写风格,与前面struct一节一样,都没有type关键字)

多个类型可以实现相同的接口(译注:意思就是这些类型的方法集有交集)。例如,如果S1和S2都有如下的方法集:

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

那么S1和S2都实现了File接口。

既然如此,一个类型自然也实现了某个实现接口的任意子集所对应的接口。例如,任意类型都实现了空接口:

interface{}

例如,通过类型声明(type declaration,译注:关于什么是type declaration可参考相应章节)定义一个叫做Locker的接口:

type Locker interface {
	Lock()
	Unlock()
}

如果S1和S2有如下两个方法:

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

那么它们分别实现了Locker接口和File接口。

一个接口T可以使用接口类型名E来替代方法规范,这叫做在T中内嵌接口E。这会将E所有的方法添加到接口T中:

type ReadWriter interface {
	Read(b Buffer) bool
	Write(b Buffer) bool
}
type File interface {
	ReadWriter  // same as adding the methods of ReadWriter
	Locker      // same as adding the methods of Locker
	Close()
}
type LockedFile interface {
	Locker
	File        // illegal: Lock, Unlock not unique
	Lock()      // illegal: Lock not unique
}

下面两种方式是非法的:

// illegal: Bad cannot embed itself
type Bad interface {
	Bad
}
// illegal: Bad1 cannot embed itself using Bad2
type Bad1 interface {
	Bad2
}
type Bad2 interface {
	Bad1
}

7.11、Map types

一个map是一种类型的元素的无序组,称为元素类型(element type)。元素类型被另一种类型的唯一键的集合索引,称为键类型(key type)。未初始化map的值为nil

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

比较操作 ==!= 对于键类型来说,必须完全已定义的(fully defined)。因此,键类型不能是函数、map或者slice。如果键类型是接口类型,对于动态键的值来说,这两个比较操作必须是已定义的,否则会造成运行时panic。

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

map的元素(element)数量称为map的长度。对于一个map m,可以使用内置函数len(m)求得其长度,在执行时可能发生变化。元素(element):

一个新的、空的map值使用内置函数make()创建,如下:

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

如上,初始容量100不会限制map的大小,map会根据存储的数量适当的增长,除了nil map。nil就像空map,但是不能添加元素。

7.12、Channel types

channel提供了一种在并发执行函数之间通过收发特定元素类型值来沟通的机制。为初始化channel的值为nil。

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

操作符 <- 指定了channel的方向,发送或者接受。如果不指明方向,就是双向的。通过赋值或显示转换,channel可以被限制为仅能发送或仅能接收。

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

一个新的、已初始化的channel值可以通过内置函数make()创建,如下:

make(chan int, 100)

channel的容量就是channel中buffer的大小。如果容量为0或者是空,这个channel就是非缓冲的,如果当发送者和接受者都准备好了,才能成功通信(译注:可以在脑海里想象一下unbuffered情况下的通信步骤)。否则,这个channel是缓冲的。一个nil channel永远不能用来通信。

channel可以用内置函数close()来关闭。 接收操作符的多值赋值形式表明来在信道关闭前接收到的值是否已经被发送了。

可以在一个单独的channel上执行发送和接受操作,在多个goroutine之间使用cap()len()内置函数无须同步。channel表现得像一个先进先出的队列。

8、Properties of types and values

8.1、Type identity

8.2、Assignability

8.3、Representability

9、Blocks

一个块(block)是花括号内的声明序列和语句。

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

除了源码中的显示块,也有一些隐式块的例子:

(译注:上面的块范围在逐渐变小)

Blocks nest and influence scoping.

10、Declarations and scope

一个声明(declaration)将常量、类型、变量、函数、label、包(package)与一个非空标识符相关联。程序中的每一个标识符必须被声明。一个块内不能连续两次声明同一个标识符。一个标识符不能即声明在文件块内,又声明在包块内。

在一个声明中,空白标识符和其他标识符一样使用,但是不会产生绑定(binding),因此也不是已声明的(declared)。在包块中,标识符初始化可以用在初始化函数声明中。

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

The scope of a declared identifier is the extent of source text in which the identifier denotes the specified constant, type, variable, function, label, or package.

Go使用块来确定作用域:

在一个块中声明的标识符可以在其内块中再次声明。当内部声明的标识符在作用域内时,它表示内部声明所声明的实体。

包子句(package clause)不是声明,包名不属于任何作用域。它是为了标识属于同一包的不同文件。

10.1、Label scopes

label被声明在label语句中,例如“break”、“continue”、“goto”。定义一个从不使用的label是非法的。与其他标识符不同的是,label不是以块为作用域,与其他非label不冲突。label的作用域是函数体。

10.2、Blank identifier

空白标识符使用 _ 表示。它是一种匿名占位符(anonymous placeholder)。

10.3、Predeclared identifiers

下面的标识符是隐式已定义的(作用域是宇宙块):

Types:
	bool byte 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 close complex copy delete imag len
	make new panic print println real recover

10.4、Exported identifiers

一个可导出的标识符允许其他包使用它。一个标识符可导出,必须满足下面两个条件:

所有其他的标识符都是不可导出的。

10.5、Uniqueness of identifiers

标识符不同即指它们的拼写不同,或者如果它们的拼写相同,但是出现在不同的包且不可导出。

10.6、Constant declarations

A constant declaration binds a list of identifiers (the names of the constants) to the values of a list of constant expressions. The number of identifiers must be equal to the number of expressions, and the nth identifier on the left is bound to the value of the nth expression on the right.

常量声明将一些标识符和一些常量表达式绑定。

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

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

If the type is present, all constants take the type specified, and the expressions must be assignable to that type. If the type is omitted, the constants take the individual types of the corresponding expressions. If the expression values are untyped constants, the declared constants remain untyped and the constant identifiers denote the constant values. For instance, if the expression is a floating-point literal, the constant identifier denotes a floating-point constant, even if the literal’s fractional part is zero.

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

Within a parenthesized const declaration list the expression list may be omitted from any but the first ConstSpec. Such an empty list is equivalent to the textual substitution of the first preceding non-empty expression list and its type if any. Omitting the list of expressions is therefore equivalent to repeating the previous list. The number of identifiers must be equal to the number of expressions in the previous list. Together with the iota constant generator this mechanism permits light-weight declaration of sequential values:

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

10.7、Iota

10.8、Type declarations

10.9、Variable declarations

10.10、Short variable declarations

10.11、Function declarations

10.12、Method declarations

11、Expressions

11.1、Operands

11.2、Qualified identifiers

11.3、Composite literals

11.4、Function literals

11.5、Primary expressions

11.6、Selectors

11.7、Method expressions

11.8、Method values

11.9、Index expressions

11.10、Slice expressions

11.11、Type assertions

11.12、Calls

11.13、Passing arguments to … parameters

11.14、Operators

11.15、Arithmetic operators

11.16、Comparison operators

11.17、Logical operators

11.18、Address operators

11.19、Receive operator

11.20、Conversions

11.21、Constant expressions

11.22、Order of evaluation

12、Statements

12.1、Terminating statements

12.2、Empty statements

12.3、Labeled statements

12.4、Expression statements

12.5、Send statements

12.6、IncDec statements

12.7、Assignments

12.8、If statements

12.9、Switch statements

12.10、For statements

12.11、Go statements

12.12、Select statements

12.13、Return statements

12.14、Break statements

12.15、Continue statements

12.16、Goto statements

12.17、Fallthrough statements

12.18、Defer statements

13、Built-in functions

13.1、Close

13.2、Length and capacity

13.3、Allocation

13.4、Making slices, maps and channels

13.5、Appending to and copying slices

13.6、Deletion of map elements

13.7、Manipulating complex numbers

13.8、Handling panics

13.9、Bootstrapping

14、Packages

14.1、Source file organization

14.2、Package clause

14.3、Import declarations

14.4、An example package

15、Program initialization and execution

15.1、The zero value

15.2、Package initialization

15.3、Program execution

16、Errors

17、Run-time panics

18、System considerations

18.1、Package unsafe

18.2、Size and alignment guarantees