新手常犯的 Go 语法错误,一次性帮你避坑

新手常犯的 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 快十倍。

相关推荐
子兮曰5 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰5 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
爱勇宝5 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码5 天前
别再前后端各写一套表单校验了
java·后端
大勇前进5 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu5 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile5 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go
大白805 天前
PHP 内存溢出排查思路:看懂报错日志,精准定位问题
后端
二月龙5 天前
PHP 接口返回统一响应封装,让前后端对接更省心
后端
盖伦发发5 天前
软件工程SOLID 五大设计原则
后端·软件工程