Skip to content
Go
builtin — Go 预声明标识符

builtin — Go 预声明标识符

builtin 不是可导入的包,而是 Go 语言预声明的标识符集合。这些标识符在任何位置都可以直接使用,无需导入。


1. 预声明类型

// 数值类型
bool
string
int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr
byte    // = uint8
rune    // = int32
float32 float64
complex64 complex128

// 特殊类型
error   // interface { Error() string }
any     // = interface{}(Go 1.18+)

int 和 uint 在 64 位系统上是 64 位,在 32 位系统上是 32 位。需要精确位宽时(文件格式、网络协议、二进制序列化),必须用 int32/int64 等确定宽度的类型——依赖 int 的宽度会让同一个数据结构在不同平台上产生不同的字节布局,这是跨平台持久化格式的经典事故源。


2. 预声明函数

2.1 内存分配:new vs make

这是 Go 新手最容易混淆的两个函数。

特性 new(T) make(T, ...)
适用类型 任意类型 仅 slice/map/chan
返回值 *T(指针) T(本身,非指针)
是否初始化 零值填充 初始化内部数据结构
使用场景 获取一个零值的指针 创建可用的 slice/map/chan
// new — 分配零值 + 返回指针
p := new(int)      // p 是 *int,指向的值是 0
*p = 42

// make — 初始化内部结构 + 返回值本身
s := make([]int, 5, 10)    // slice: len=5, cap=10
m := make(map[string]int)  // map: 可用的空 map
ch := make(chan int, 10)   // channel: 缓冲区 10

Go 1.26 起 new 的入参可以是一个表达式,用来指定初始值。这在给"指针表示可选字段"的序列化结构赋值时特别方便:

type Person struct {
    Name string
    Age  *int   // 可选:nil 表示未知
}

age := yearsSince(birth)
p := Person{Name: "Alice", Age: new(age)}  // Age 指向 age 的一份副本

易错:new(expr) 只接受一个表达式。new(MyStruct, "a", 1) 是错误写法,编译直接报 “too many arguments for new”。要构造带字段的结构体指针,仍然用 &MyStruct{...}。

2.2 集合操作

// append — 向 slice 添加元素
s := []int{1, 2}
s = append(s, 3, 4)       // 可变参数
s = append(s, other...)   // 展开另一个 slice

// copy — 复制 slice 元素(返回复制的数量)
dst := make([]int, len(src))
n := copy(dst, src)        // n = min(len(dst), len(src))

// delete — 从 map 删除键
delete(myMap, key)          // 即使 key 不存在也不报错

// clear — 清空 slice/map(Go 1.21+)
clear(m)                    // 删除 map 所有键;对 slice 则把所有元素置零值

// len — 返回长度
len(slice)    // slice 长度
len(map)      // map 键值对数量
len(channel)  // channel 缓冲区中的元素数

// cap — 返回容量
cap(slice)    // slice 底层数组的容量
cap(channel)  // channel 缓冲区大小

注意:var m map[string]int 声明的是 nil map,可以读但不能写。写入 nil map 会 panic,必须 make(map[string]int) 才能写。而 nil slice 可以 append,两者行为不同——这是新手最常踩的第一坑。

如果知道 slice 的最终大小,用 make([]T, 0, capacity) 预分配容量,避免 append 过程中反复扩容与拷贝。

2.3 数值内建函数 min / max

Go 1.21 把 min/max 提升为内建函数,接受任意多个可比较(有序)参数,省去手写比较:

a, b, c := 3, 7, 1
fmt.Println(min(a, b, c), max(a, b, c))  // 1 7

// 对浮点,NaN 会"传染":只要有一个参数是 NaN,min 和 max 都返回 NaN。
// (这与 IEEE minNum/maxNum "忽略单个 NaN" 的语义不同,别想当然。)

2.4 异常处理:panic / recover

// panic — 立即终止当前 goroutine 的正常执行
func must(i int, err error) int {
    if err != nil {
        panic(err)
    }
    return i
}

// recover — 只能在 defer 中捕获 panic
func safeCall(f func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic recovered: %v", r)
        }
    }()
    f()
    return nil
}

陷阱:

  • recover 只有在 defer 中直接调用的函数里才有效。defer func() { recover() }() 有效;defer recover() 无效(recover 需要运行在被 panic 的函数栈帧的 defer 上下文中)。
  • 一个 goroutine 的 panic 不会传播到其他 goroutine——它会终止整个进程,除非某处 recover 了。
  • 不要用 panic 做常规错误处理。panic 留给程序逻辑不可能恢复的场景(如初始化失败),业务错误一律用返回 error。

2.5 Channel 操作:close

ch := make(chan int, 10)
ch <- 1
close(ch)

// 关闭后:
// - 可以继续接收剩余数据(直到缓冲排空)
// - 接收已关闭且排空的 channel 得到零值 + false
val, ok := <-ch  // ok == false 表示已关闭且排空

最佳实践:发送方负责关闭 channel,接收方永远不关闭。向已关闭的 channel 发送、或重复 close 都会 panic。close 的作用是给接收方一个"数据流结束"的广播信号,因此只有知道"不再有数据"的那个 goroutine 才有权关闭。

2.6 类型转换

// 数值转换
i := int(3.14)     // 截断为 3(不是四舍五入)
f := float64(42)   // 显式转换

// 字符串与字节/字符的转换
s := string([]byte{'h', 'e', 'l', 'l', 'o'})  // []byte → string
b := []byte(s)                                 // string → []byte(复制!)
r := []rune(s)                                 // string → rune slice(正确处理 Unicode)

[]byte(s) 和 string(b) 都会复制数据,不是零成本转换。Go 1.22 起编译器会在把 []byte 转成 string 后仅用于只读比较等场景时做优化,避免复制。追求零拷贝时标准库用 unsafe.String/unsafe.Slice(Go 1.20),但要保证底层内存生命周期。


3. 预声明常量

true  false  // bool
iota         // 自增计数器(仅 const 块内)
nil          // 零值(指针、接口、map、slice、chan、func)

iota 详解

const (
    _ = iota                 // 0(跳过)
    KB = 1 << (10 * iota)    // 1 << 10 = 1024
    MB                        // 1 << 20 = 1048576
    GB                        // 1 << 30
)

// iota 每行递增,每个 const 块开始时重置
const (
    A = iota  // 0
    B         // 1
    C         // 2
)

iota 只在 const 块内有效,每个源码行(而非每个常量)递增一次。同一行 const 里的多个表达式共享同一个 iota 值。后续未显式赋值的行会隐式重复上一行的表达式,这正是 KB/MB/GB 能自动递增的原因。

nil 的类型

nil 本身没有类型,它在使用时获得目标类型:

  • 指针:(*int)(nil)
  • 接口:(error)(nil)
  • slice:([]int)(nil)
  • map:(map[string]int)(nil)
  • chan:(chan int)(nil)
  • func:(func())(nil)

陷阱:var w io.Writer = (*os.File)(nil) 之后 w == nil 为 false。这是 Go 中最经典的坑之一——接口是否为 nil,要求动态类型和动态值都是 nil。

深入理解:接口的 nil 判断

接口在 Go 内部由两部分组成:动态类型 + 动态值。

接口值 = (type, value)

真正的 nil 接口:  (nil, nil)        →  w == nil 为 true
"有类型的 nil":   (*os.File, nil)   →  w == nil 为 false!

只有当 type 和 value 都是 nil 时,接口才等于 nil。

// 场景演示
var f *os.File = nil                    // f 是 nil 指针
var w io.Writer = f                     // w = (*os.File, nil),不是 nil 接口
fmt.Println(w == nil)                   // false —— 这就是那个惊天大坑

var w2 io.Writer = nil                  // w2 = (nil, nil),真正的 nil 接口
fmt.Println(w2 == nil)                  // true

如何正确判断:

// 方法 1:直接和 nil 比较 —— 只对真正的 nil 接口有效
if w != nil {
    // 即使底层动态值是 nil,这里也会进入
}

// 方法 2:反射判断 —— 可检测"有类型的 nil"
func isNil(v any) bool {
    if v == nil {
        return true
    }
    rv := reflect.ValueOf(v)
    switch rv.Kind() {
    case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan, reflect.Func, reflect.Interface:
        return rv.IsNil()
    }
    return false
}

根治办法:别让函数把 nil 具体值赋给接口再返回。

// 容易出错的写法
func GetWriter() io.Writer {
    return getFile()  // getFile() 返回 nil *os.File → 得到 (*os.File)(nil),非 nil 接口
}

// 正确写法
func GetWriter() io.Writer {
    f := getFile()
    if f == nil {
        return nil    // 显式返回 nil 接口
    }
    return f
}

记忆口诀:不要把 nil 指针赋给接口再返回。函数返回接口类型时,若具体类型可能是 nil,应显式 return nil。用静态检查器(如 nilness、golangci-lint 的相关规则)能在 CI 阶段拦住这类问题。


4. 实战建议

  • new/make 记住一句话:要指针用 new,要能用的容器用 make。实际项目里 new 用得很少,多数场景直接 &T{} 取地址更清晰。
  • 处理跨平台二进制格式,绝不用 int,一律 int32/uint64 显式定宽。
  • 接口 nil 判断是 Go 面试与线上事故的双重高频点——写返回 error 的函数时,要么返回真正的 nil,要么返回包装了具体错误的变量,别中途把类型化 nil 赋给 error 变量。
  • 预分配容量是 Go 里性价比最高的一类优化,尤其在循环 append 前。