本文档全面概述了 Go 的语法和类型系统,涵盖了语言的词法结构、语法元素以及类型系统的基本原理。本材料着重介绍 Go 的语法定义方式以及语言规范中类型系统的结构。有关类型检查系统的实现信息,请参阅 语言规范和文档。
Go 是一种静态类型语言,其语法在很大程度上源自 C,但进行了显著的简化。该语言的设计旨在易于解析和理解,从而实现更快的编译速度和更高的开发人员生产力。
Go 的语法使用扩展巴科斯范式(EBNF)的一个变体来定义,这为描述语言语法提供了正式的表示法。语法在设计上是紧凑且规则化的,使得该语言易于通过自动化工具进行分析。
Go 源代码是使用 UTF-8 编码的 Unicode 文本。与一些要求规范形式的语言不同,Go 将每个 Unicode 码点视为不同的。语言规范使用“字符”一词来指代源代码文本中的 Unicode 码点。
Go 定义了几类字符
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 类别 Lu、Ll、Lt、Lm 或 Lo 中的任何 Unicode 字符。此外,下划线字符(_)在 Go 中被视为小写字母。
Go 的标记构成了语言的词汇,分为标识符、关键字、运算符和标点符号以及字面量。空格(空格、制表符、回车符和换行符)被忽略,除非它们用于分隔标记。
来源: doc/go_spec.html104-135 doc/go_spec.html163-177
Go 支持两种形式的注释
// 开头并延续到行尾的行注释/* 开头并以 */ 结尾的通用注释注释不能嵌套,也不能在字符串或 rune 字面量内部开始。
Go 的语法在许多产生式中使用分号作为终止符,但该语言允许在源代码中省略其中大部分分号。Go 编译器会根据特定规则自动插入分号
如果在行的最后一个标记后,并且该标记是以下之一,则插入分号:
break、continue、fallthrough、return)++、--、)、]、})在闭合的 ) 或 } 之前可以省略分号
这种自动分号插入使得 Go 拥有简洁的、基于花括号的语法,同时保持了正式的语法定义。
标识符用于命名程序实体,如变量和类型。标识符是由一个或多个字母和数字组成的序列,第一个字符必须是字母。
identifier = letter { letter | unicode_digit } .
有效标识符示例
a
_x9
ThisVariableIsExported
αβ
在 Go 中,以大写字母开头的标识符是导出的(在包外可见),而小写标识符是包私有的。
Go 语言有 25 个保留关键字,不能用作标识符
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 支持几种类型的字面量
整数字面量:代表整数常量的数字序列,可选择性地带有非十进制基数的前缀(0b/0B 表示二进制,0o/0O 表示八进制,0x/0X 表示十六进制)。下划线可用于提高可读性。
浮点数字面量:浮点数常量的十进制或十六进制表示。十进制形式由整数部分、小数点、小数部分和指数部分组成。十六进制形式使用 0x 前缀和 p 指数。
虚数字面量:表示复数常量的虚部,由整数或浮点数字面量后跟 i 组成。
Rune 字面量:表示一个 rune 常量(Unicode 码点),表示为用单引号括起来的一个或多个字符。
字符串字面量:两种形式
`...`)括起来的原始字符串字面量"...")括起来的解释型字符串字面量Go 中的类型决定了一组值以及与这些值相关的特定操作和方法。类型可以是命名的,也可以是未命名的(字面量类型)。语言预先声明了某些类型名称,其他类型则通过类型声明或类型参数列表引入。
Go 类型分为:
来源: doc/go_spec.html799-830 src/go/types/predicates.go16-31 src/cmd/compile/internal/types2/predicates.go16-26
Go 有几种预声明的基本类型
布尔类型:bool - 布尔真值集(true 和 false)
数值类型:
int8、int16、int32(别名:rune)、int64、int(平台相关的大小);uint8(别名:byte)、uint16、uint32、uint64、uint、uintptrfloat32、float64complex64、complex128字符串类型:string - 不可变的字节序列
这些类型是已定义类型,彼此之间互不相同,但存在别名,如 byte(uint8 的别名)和 rune(int32 的别名)。
Go 提供了几种从其他类型构建类型的方式
[n]T - 由 T 类型组成的 n 个元素的编号序列[]T - 描述底层数组段的描述符*T - 指向 T 类型值的指针map[K]V - 由类型为 K 的键索引的、类型为 V 的元素的无序集合chan T - 用于类型为 T 的值的通信通道来源: src/go/types/expr.go17-57 src/cmd/compile/internal/types2/expr.go17-57 src/go/types/decl.go49-87 src/cmd/compile/internal/types2/decl.go49-87 src/go/types/stmt.go17-55 src/cmd/compile/internal/types2/stmt.go17-54 src/go/types/typeset.go17-23 src/cmd/compile/internal/types2/typeset.go17-23
Go 编译器使用复杂的类型检查机制来实现类型系统。该实现包含几个关键组件,它们协同工作以强制执行类型安全。
Go 中的类型检查遵循以下主要步骤:
类型检查器以递归方式处理 Go 代码,从顶级声明开始,向下移动到表达式和语句。
来源: src/go/types/expr.go17-57 src/cmd/compile/internal/types2/expr.go17-57 src/go/types/decl.go49-87 src/cmd/compile/internal/types2/decl.go49-87 src/go/types/stmt.go17-55 src/cmd/compile/internal/types2/stmt.go17-54
在 Go 编译器中,类型由实现 Type 接口的结构表示。不同的具体类型实现处理 Go 类型类别的每一个:
| 类型类别 | 实现结构 |
|---|---|
| 基本类型 | *Basic |
| 数组类型 | *Array |
| 切片类型 | *Slice |
| 结构体类型 | *Struct |
| 指针类型 | *Pointer |
| 函数类型 | *Signature |
| 接口类型 | *Interface |
| Map 类型 | *Map |
| Channel 类型 | *Chan |
每个类型结构都包含类型检查所需的信息,例如数组和切片的元素类型、结构体的字段类型以及函数的参数/结果类型。
来源: src/go/types/predicates.go16-31 src/cmd/compile/internal/types2/predicates.go16-26
表达式类型检查是递归执行的,在表达式树上自底向上进行。类型检查器使用 operand 结构来评估表达式,该结构跟踪:
表达式检查的一般算法是:
来源: src/go/types/expr.go58-142 src/cmd/compile/internal/types2/expr.go58-115
Go 类型系统定义了值何时可以赋值给变量或作为参数传递的规则:
这些规则在赋值、函数调用和返回语句的类型检查期间得到强制执行。
来源: src/go/types/assignments.go21-74 src/cmd/compile/internal/types2/assignments.go21-66
类型检查器使用谓词函数来分类类型并确定有效的操作。关键谓词包括:
isBasic:测试类型是否为基本类型isInteger、isUnsigned、isFloat、isComplex、isNumeric:检查数值类型类别isString:测试类型是否为字符串类型isBoolean:测试类型是否为布尔类型isValid:测试类型是否为有效类型(非无效类型)这些谓词在类型检查过程中用于验证操作和转换。
来源: src/go/types/predicates.go16-31 src/cmd/compile/internal/types2/predicates.go16-26
Go 提供了几个内置函数,如 make、new、len、cap 等,这些函数具有特殊的类型检查规则。类型检查器包含处理这些内置函数的特定代码,验证它们的参数并确定它们的返回类型。
内置函数的类型检查确保:
例如,make 只能与切片、Map 或 Channel 类型一起调用,而 len 可用于数组、切片、字符串、Map 和 Channel。
来源: src/go/types/builtins.go19-84 src/cmd/compile/internal/types2/builtins.go19-81
函数调用类型检查包括:
对于泛型函数,类型检查器还处理类型参数的推断和实例化。
来源: src/go/types/call.go170-339 src/cmd/compile/internal/types2/call.go171-336
Go 中的接口类型定义了方法集和/或类型约束。类型检查器会验证:
随着 Go 1.18 中引入泛型,接口也充当类型约束,限制了满足类型参数的类型集合。
来源: src/go/types/typeset.go17-23 src/cmd/compile/internal/types2/typeset.go17-23
Go 拥有一个复杂的常量系统,其中包含无类型常量,它们会推迟类型承诺,直到需要时为止。类型检查器处理:
无类型常量具有默认类型,这些类型在需要显式类型时分配:无类型 bool → bool,无类型 int → int,无类型 float → float64 等。
类型检查器会为类型错误生成有意义的错误消息,并按错误代码分类。一些常见的类型错误包括:
错误报告系统提供详细的错误消息,帮助开发人员理解和修复与类型相关的问
来源: src/internal/types/errors/codes.go9-12
Go 的语法和类型系统旨在实现清晰、简洁和安全。EBNF 中定义的正式语法为语言语法奠定了坚实的基础,而静态类型系统则在不冗余的情况下确保了类型安全。Go 编译器的类型检查器实现了这些设计原则,在强制执行类型规则的同时,也提供了有用的错误消息来处理违规情况。
类型系统在灵活性和安全性之间取得了平衡,能够实现强大的抽象,同时在编译时就能防止许多常见的编程错误。随着 Go 1.18 中泛型的加入,类型系统在保持其基本原则的同时,也获得了更强的表达能力。