你知道吗?Go 语言中的 int 和 make 不是关键字!

橘子海
橘子海
发布时间:2026-08-25 | 23人浏览过
原创

前言

在 Go 里,`int`、`make`、`len` 这些名字太常见,以至于很多人会下意识把它们当成关键字。可 Go 恰恰把这类名字放在了“预定义标识符”里。弄清这层区别,不仅能解释为什么某些“离谱写法”居然合法,也能帮助你判断 Go 的语法边界、编译器处理方式,以及实际开发中哪些命名最好别碰。

在 Go 里,int、make、len 这些名字几乎天天都在用,所以很多人会默认它们就是关键字。但 Go 刻意没有这样设计。理解这件事,不只是记住一个冷知识,更能帮助你判断哪些名字属于语法本身、哪些只是预先放进作用域的标识符,以及为什么实际项目里不该随手把它们拿来当变量名。

为什么 int 和 make 不是关键字

先看一段容易让人误会、但完全合法的 Go 代码:

package main

import "fmt"

func main() {
    int := "hello"
    make := func() string { return "world" }

    fmt.Printf("int = %s, make() = %sn", int, make())
    // 输出:int = hello, make() = world
}

这段代码之所以能通过编译,正是因为 int 和 make 不是关键字。它们虽然有特殊含义,但仍然属于可以被重新声明、也可能被局部遮蔽的名字。

如果放到 C 这类语言里,int 是语法层面的保留字,这样写会直接报错。Go 在这里走的是另一条路:把真正不可替代的语法词保留得很少,把常用类型名和内建函数名放到另一套机制里处理。

关键字和预定义标识符,到底差在哪

要看懂 Go 这套设计,先得把两个概念分开:

用暖色纸张卡片对比 Go 关键字与预定义标识符,突出 int、make 属于后者
Go 名字分类怎么分把“不能重定义的语法词”和“预先声明但可被遮蔽的名字”放在一张图里,更容易看清 Go。
  • 关键字:语言语法的一部分,用来组成控制流、声明、类型定义等结构。
  • 预定义标识符:语言预先提供的名字,包括内建类型、常量和函数,但它们本质上仍是标识符。

两者在使用体验上很像,但地位不同。关键字直接参与语法解析,预定义标识符则更接近“默认已经声明好的名字”。

Go 只有 25 个关键字

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 的语法骨架。它们承担的是“句法角色”,例如声明函数、控制流程、定义类型、导入包等,因此不能拿来当普通变量名。

预定义标识符有哪些

相比之下,预定义标识符更多,大约有 30 多个,主要分成三类。

内建类型:

  • 基本类型:int, int8, int16, int32, int64
  • 无符号整数:uint, uint8, uint16, uint32, uint64, uintptr
  • 浮点数:float32, float64
  • 复数:complex64, complex128
  • 其他:bool, byte, rune, string, error

内建常量:true, false, iota, nil

内建函数:make, len, cap, new, append, copy, close, delete, panic, recover 等

也就是说,int 和 make 虽然“看起来像关键字”,但在语言分类上并不属于关键字集合。

Go 为什么要这样设计

给语言演进留空间

Go 把很多常用名字放进预定义标识符,而不是关键字,一个重要原因就是保留扩展余地。

关键字一旦加入语言语法,兼容性成本就会明显变高;而预定义标识符可以通过向 universe 块增加声明的方式扩展,语言在演进时更灵活,也更不容易破坏既有代码结构。

这也是 Go 一贯的思路:尽量缩小真正需要写进语法的部分,把更多能力留给更易维护、也更易兼容的层次。

从编译器实现看,这不是“弱化”而是“解耦”

站在编译器的角度,关键字和标识符在词法阶段都会先变成 token。不同之处在于,Go 会在初始化时把这些预定义标识符直接注入符号表,因此开发者使用它们时会感觉“几乎和关键字一样顺手”。

这种做法带来的好处很直接:

  • 语言核心语法更稳定,不必因为内建能力调整而频繁修改关键字集合。
  • 编译器实现更清晰,很多能力可以通过符号表和后续语义处理完成。
  • 后续扩展时,语言设计者拥有更大的兼容空间。

所以这不是单纯为了“关键字更少”,而是把语法和内建能力刻意分层。

实际开发里最该注意什么

能重名,不代表应该重名

既然是预定义标识符,它们就可能被局部变量重新定义,也就会引出典型的变量遮蔽问题:

用步骤便签展示 make 从源码到运行时的处理过程,说明它为何像函数又不完全是函数
make 为什么不是普通函数这张图不展示代码截图,而是用步骤卡片梳理 make 的编译期与运行期分工。
package main

import "fmt"

func main() {
    // 在函数内部重新定义int
    int := "hello"

    // 后续如果想使用整数类型int,会导致错误
    // var num int // 这行会编译错误

    fmt.Printf("类型:%T,值:%vn", int, int)
}

这类写法的危险不在于“语法奇怪”,而在于它会直接影响后续语义。你在当前作用域里把 int 当作字符串变量名之后,原本的整数类型名就被遮住了。

因此,实际项目里的建议很明确:不要重新定义这些预定义标识符。即便编译器允许,这样写也会明显降低可读性,排查问题时成本很高。

为什么 make 看起来像函数,却又不完全像函数

make 在 Go 里尤其典型。它表面上是函数调用形式,但本质上是编译器内置函数,也就是常说的 compiler intrinsic。

当编译器遇到 make(Type, args...) 时,通常会经过这样几步:

  1. 源代码解析:识别 make 是特殊的内建操作
  2. 类型检查:根据参数类型转换为特定内部符号
  3. 代码生成:将内部符号替换为运行时函数调用
  4. 运行时执行:调用真正的底层实现函数

也正因为如此,make 能针对 slice、map、chan 执行不同的底层分配与初始化逻辑。它在语法上像普通函数,在实现上却带有明显的编译器特殊处理。

和其他语言相比,Go 的关键字为什么这么少

如果把 Go 放到主流语言里比较,它的关键字数量确实相当克制:

  • C (C17): 44个关键字
  • Java (SE 17): 67个关键字
  • Python 3 (3.10): 38个关键字
  • Go (1.18): 25个关键字

关键字少,不只是为了“好记”。更关键的是,Go 希望把语言核心控制在一个较小、较稳定的范围内,让开发者更容易掌握语法边界,同时把复杂度转移到更容易维护的实现层和标准能力层。

回到开头那个问题,int 和 make 不是关键字,看起来像小细节,其实正好体现了 Go 的设计取向:语法尽量简,能力要够用,还要为未来扩展保留余地。这也是为什么 Go 的很多规则初看反直觉,理解之后却会觉得它们相当统一。

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表