
在 Go 中,对结构体字段进行赋值时,左操作数必须是“可寻址的变量”(addressable variable);若函数返回结构体值(而非指针),其结果是临时匿名值,不可寻址,因此 f().x = v 会编译失败;而返回指针时,f()->x 实际是对底层可寻址内存的解引用赋值,合法且有效。
在 go 中,对结构体字段进行赋值时,左操作数必须是“可寻址的变量”(addressable variable);若函数返回结构体值(而非指针),其结果是临时匿名值,不可寻址,因此 `f().x = v` 会编译失败;而返回指针时,`f()->x` 实际是对底层可寻址内存的解引用赋值,合法且有效。
Go 的赋值语句要求左操作数(LHS)必须是一个可寻址的变量(如变量名、指针解引用、切片索引、结构体字段访问等),即编译器能为其生成有效的内存地址。这是语言层面的强制约束,目的是确保赋值具有可观测的副作用——否则修改一个转瞬即逝的临时值毫无意义。
考虑如下精简示例:
package main
type Employee struct {
ID int
Salary float64
}
func EmployeeByID(id int) *Employee {
return &Employee{ID: id, Salary: 8000}
}
func EmployeeByIDValue(id int) Employee {
return Employee{ID: id, Salary: 8000}
}
func main() {
dilbert := Employee{ID: 123}
// ✅ 合法:EmployeeByID 返回 *Employee,*Employee 是可寻址的
EmployeeByID(dilbert.ID).Salary = 0 // 修改堆/全局中真实 Employee 的字段
// ❌ 编译错误:EmployeeByIDValue 返回 Employee 值,是不可寻址的临时值
// EmployeeByIDValue(dilbert.ID).Salary = 0 // cannot assign to EmployeeByIDValue(...).Salary
// ✅ 合法(绕过方式):先绑定到局部变量,使其可寻址
emp := EmployeeByIDValue(dilbert.ID)
emp.Salary = 0 // 修改的是 emp 的副本,不影响原数据
}关键点在于:
- EmployeeByID(id) 返回 *Employee → 解引用 (*Employee).Salary 是对某块已分配内存中字段的直接写入,该内存可能来自堆、全局变量或闭包捕获,具有持久状态;
- EmployeeByIDValue(id) 返回 Employee → 表达式 EmployeeByIDValue(...).Salary 中,结构体本身是纯右值(r-value),无固定内存地址,Go 禁止对其字段赋值,避免“静默丢弃修改”。
⚠️ 注意事项:
- 即使结构体字面量本身可寻址(如 &struct{X int}{1}.X 合法),但函数调用返回的结构体值永远不可寻址,这是 Go 的明确规则(见 Go Language Specification: Address operators);
- 不要误以为“结构体字段访问总是可赋值”——是否可赋值取决于整个左值表达式的可寻址性,而非字段本身;
- 若需链式修改且依赖值语义,应显式接收并返回新结构体(函数式风格),而非尝试就地修改临时值。
总结:Go 通过限制赋值目标为可寻址实体,在语言层保障了内存安全与语义清晰性。理解 T 与 *T 在赋值上下文中的根本差异,是写出健壮、符合 Go 惯用法代码的基础。


















