请解释短变量声明 `:=` 与 `var` 声明在作用域与重新声明上的差异

解读

面试官抛出此题,往往不是为了背语法,而是考察候选人是否真正踩过“变量遮蔽(variable shadowing)”与“重复声明”这两个线上坑。国内一线互联网厂的高并发服务里,一个因作用域误用导致的 nil 指针或资源泄漏就可能让全链路超时,因此对作用域边界的敏感度是区分“写业务”与“写基础设施”的重要标尺。

知识点

  1. 作用域规则

    • var 只能出现在包级函数级代码块,显式指定作用域边界
    • := 只能出现在函数内部,且每次出现都至少有一个新变量被创建;老变量若在同一作用域内则仅赋值,若跨作用域则产生遮蔽
  2. 重新声明(redeclaration)

    • var 在同一作用域内绝对禁止重复标识符,编译期报错。
    • := 允许多变量并列时对已有变量“部分赋值”,但前提是至少有一个新变量被引入,否则编译失败:“no new variables on left side of :=”。
  3. 初始化与零值

    • var显式赋予零值(int 为 0,指针为 nil)。
    • := 必须能推导出初始值,否则无法通过编译。
  4. 包级可见性

    • 包级只能用 var(或 const/type),不能用 :=,否则报“syntax error: non-declaration statement outside function body”。
  5. 实战陷阱

    • for-range、if、switch 等隐式代码块内使用 := 极易产生遮蔽,导致外部变量未如预期被修改。
    • 并发场景下,goroutine 捕获循环变量时若误用 :=,会让闭包抓到错误的副本,造成数据竞争。

答案

var:= 的核心差异体现在作用域边界重新声明策略两点。
第一,作用域:var 可用于包级或函数级,显式划定作用域;而 := 只能出现在函数内部,且会创建新作用域并可能遮蔽外层同名变量
第二,重新声明:var 在同一作用域内禁止重复标识符:= 则允许多变量并列时对旧变量二次赋值,但至少引入一个新变量,否则编译器报错。
线上经验告诉我们,for-range、if 初始化语句里误用 := 是产生 nil 指针与资源泄漏的高危场景,必须显式确认是否需要修改外层变量,必要时提前 var 声明或采用临时变量规避遮蔽。”

拓展思考

  1. 在 Kubernetes 的 controller 循环里,若使用
    for _, pod := range podList { go func() { ... }() }
    直接 := 会导致所有 goroutine 捕获最后一个 pod 指针。正确姿势是显式传参函数内再 var 赋值,阻断闭包变量共享。

  2. 从 Go1.18 起,泛型代码里 :=类型推导遮蔽规则依旧不变;但实例化后的类型参数可能让零值判断更隐蔽,建议对泛型约束接口中的指针类型一律先 var 声明零值,再二次赋值,避免 := 推导成非预期具体类型。