新手常犯的 Go 语法错误,一次性帮你避坑
1. := 和 = 搞混
最常见的第一坑。
go
go
// ❌ 报错:no new variables on left side of :=
count = count + 1
// ✅ 正确
count += 1
规则 :函数体外部只能用 =,函数体内部第一次赋值用 :=,之后用 =。
2. 声明了变量却不用
go
go
// ❌ 编译报错:declared and not used
var name = "golang"
// ✅ 方案一:用掉它
fmt.Println(name)
// ✅ 方案二:用 _ 忽略
_ = name
Go 不允许有"死变量",这不是建议,是硬规则。
3. short var 声明时,左边必须有至少一个新变量
go
go
// ❌ 报错:no new variables on left side of :=
x := 1
x, y := 2, 3 // x 已存在,不能用 :=
// ✅
x, y = 2, 3 // 用 = 重新赋值
4. 忘记 return 的命名返回值会返回零值
go
go
func divide(a, b float64) (result float64, err error) {
if b == 0 {
return // ❌ err 是 nil,result 是 0,调用方无法判断是真的返回了 0 还是出错了
}
return a / b, nil
}
// ✅
if b == 0 {
return 0, errors.New("division by zero")
}
这是新手最容易踩的逻辑坑------不报错,但行为完全不对。
5. 切片(slice)引用同一底层数组
less
go
// ❌ 两个切片共享底层数组,改一个全变
a := []int{1, 2, 3}
b := a
b[0] = 99
fmt.Println(a) // [99 2 3]
// ✅ 需要拷贝
b := make([]int, len(a))
copy(b, a)
// ✅ 或直接切(但注意容量)
b := a[:] // 仍然共享!
b := a[:len(a):len(a)] // 限制容量,append 时才不互相影响
6. for 循环变量捕获问题(Go 1.22 之前)
go
go
// ❌ 输出的全是 3(Go 1.21 及以前)
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i) // 闭包捕获的是同一个 i
}()
}
// ✅ 传参
for i := 0; i < 3; i++ {
go func(v int) {
fmt.Println(v)
}(i)
}
Go 1.22 已修复此行为,循环变量在每次迭代中独立。但如果你还在用旧版本,这个坑必须知道。
7. nil slice 和空 slice 不一样
go
go
var s1 []int // nil slice
s2 := []int{} // empty slice
s3 := make([]int, 0) // empty slice
fmt.Println(s1 == nil) // true
fmt.Println(s2 == nil) // false
fmt.Println(len(s1)) // 0
fmt.Println(cap(s1)) // 0
// JSON 序列化时差异明显
json.Marshal(s1) // null
json.Marshal(s2) // []
建议 :返回空集合时,优先返回 nil,避免 JSON 输出不一致。
8. 结构体字段首字母小写 = 私有
go
go
type User struct {
Name string // ✅ 导出(大写开头)
age int // ❌ 私有,包外无法访问
}
// ✅ 命名规范:需要导出就大写,不需要就小写,别随心所欲
这不是"语法错误",但 90% 的新手在包间传递数据时撞墙,都是因为这个。
9. defer 的执行顺序搞反了
go
go
func example() {
defer fmt.Println("A")
defer fmt.Println("B")
}
// 输出:B A(后进先出,像栈)
如果你写 defer file.Close() 之后又写了 defer file.Write(...),文件可能在还没写完时就关了。
10. select 里的 default 不是"随便执行"
go
go
select {
case ch <- 1:
fmt.Println("sent")
default:
fmt.Println("nobody listening")
}
default 只在所有 case 都不可用时 立即执行。如果你想"等一等再决定",不要用 select,用 time.After。
速查表
| 错误 | 现象 | 解决 |
|---|---|---|
:= 用在已有变量 |
编译报错 | 改用 = |
| 变量声明不用 | 编译报错 | 用 _ 忽略或删掉 |
| 命名返回值忘赋值 | 逻辑错误 | 显式 return 零值+err |
| 切片共享底层数组 | 数据被意外修改 | copy() 或重新分配 |
| for 闭包捕获 i | 输出全是最后的值 | 传参或 Go 1.22+ |
| nil vs empty slice | JSON 输出不一致 | 统一用 nil |
| 字段小写 | 包外访问不了 | 大写首字母导出 |
这些坑不复杂,但每个都够你调半小时。收藏这篇,写代码时对照着查,比事后 debug 快十倍。