请解释短变量声明 `:=` 与 `var` 声明在作用域与重新声明上的差异
解读
面试官抛出此题,往往不是为了背语法,而是考察候选人是否真正踩过“变量遮蔽(variable shadowing)”与“重复声明”这两个线上坑。国内一线互联网厂的高并发服务里,一个因作用域误用导致的 nil 指针或资源泄漏就可能让全链路超时,因此对作用域边界的敏感度是区分“写业务”与“写基础设施”的重要标尺。
知识点
-
作用域规则
var只能出现在包级或函数级代码块,显式指定作用域边界。:=只能出现在函数内部,且每次出现都至少有一个新变量被创建;老变量若在同一作用域内则仅赋值,若跨作用域则产生遮蔽。
-
重新声明(redeclaration)
var在同一作用域内绝对禁止重复标识符,编译期报错。:=允许多变量并列时对已有变量“部分赋值”,但前提是至少有一个新变量被引入,否则编译失败:“no new variables on left side of :=”。
-
初始化与零值
var会显式赋予零值(int 为 0,指针为 nil)。:=必须能推导出初始值,否则无法通过编译。
-
包级可见性
- 包级只能用
var(或 const/type),不能用:=,否则报“syntax error: non-declaration statement outside function body”。
- 包级只能用
-
实战陷阱
- for-range、if、switch 等隐式代码块内使用
:=极易产生遮蔽,导致外部变量未如预期被修改。 - 并发场景下,goroutine 捕获循环变量时若误用
:=,会让闭包抓到错误的副本,造成数据竞争。
- for-range、if、switch 等隐式代码块内使用
答案
“var 与 := 的核心差异体现在作用域边界与重新声明策略两点。
第一,作用域:var 可用于包级或函数级,显式划定作用域;而 := 只能出现在函数内部,且会创建新作用域并可能遮蔽外层同名变量。
第二,重新声明:var 在同一作用域内禁止重复标识符;:= 则允许多变量并列时对旧变量二次赋值,但至少引入一个新变量,否则编译器报错。
线上经验告诉我们,for-range、if 初始化语句里误用 := 是产生 nil 指针与资源泄漏的高危场景,必须显式确认是否需要修改外层变量,必要时提前 var 声明或采用临时变量规避遮蔽。”
拓展思考
-
在 Kubernetes 的 controller 循环里,若使用
for _, pod := range podList { go func() { ... }() }
直接:=会导致所有 goroutine 捕获最后一个 pod 指针。正确姿势是显式传参或函数内再 var 赋值,阻断闭包变量共享。 -
从 Go1.18 起,泛型代码里
:=的类型推导与遮蔽规则依旧不变;但实例化后的类型参数可能让零值判断更隐蔽,建议对泛型约束接口中的指针类型一律先var声明零值,再二次赋值,避免:=推导成非预期具体类型。