Embedding 是 Go 实现组合的唯一正统路径,需匿名字段、导出成员、正确初始化指针及合理语义关系。

Embedding 不是继承,也不是语法糖包装,它是 Go 实现组合的唯一正统路径——用好了省 80% 的样板代码,用错了连编译都过不了。
嵌入字段必须匿名,命名就失效
很多人写 type Admin struct { user User },以为这是“嵌入”,结果 admin.Name 报错,admin.Validate() 找不到。这不是 bug,是故意设计:只有匿名字段(即只写类型名,不写字段名)才触发提升。
-
type Admin struct { User }→ ✅ 字段和导出方法自动提升 -
type Admin struct { u User }→ ❌ 只能写admin.u.Name、admin.u.Validate() - 嵌入字段名若与外层字段同名(如
type Admin struct { User; Name string }),则admin.Name永远取外层字段,User.Name被遮蔽
指针嵌入必须显式初始化,否则 panic
写 type Service struct { *Logger } 很常见,但 s.Log("msg") 会直接 panic: runtime error: invalid memory address ——因为 *Logger 是 nil 指针,Go 不会自动 new 它。
- 值嵌入(
Logger)可直接赋值:Service{Logger: Logger{...}} - 指针嵌入(
*Logger)必须初始化:Service{Logger: &Logger{...}}或Service{Logger: new(Logger)} - 构造函数里统一初始化最安全:
func NewService() *Service { return &Service{Logger: &Logger{}} }
方法提升只对导出成员生效,小写字母字段/方法全丢
嵌入 type Config struct { timeout int; Load() {} } 到另一个结构体后,c.timeout 和 c.Load() 都不可见——Go 只提升首字母大写的导出成员。
- 字段名必须大写:
Timeout int才能被提升 - 方法名必须大写:
func (c Config) Load() {}才会被外层调用 - 接收者类型影响调用方式:如果
Load()是指针接收者func (c *Config) Load(),那只有*Service能调用,Service{}值变量会报错
接口嵌入不是继承,而是方法集扁平合并
写 type ReadCloser interface { Reader; Closer } 看起来像继承,其实 Go 编译器只是把 Reader 和 Closer 的所有方法签名拉平到 ReadCloser 里,没有父子关系,也没有重写机制。
立即学习“go语言免费学习笔记(深入)”;
- 不能嵌入具体类型:
interface { *bytes.Buffer }❌ 编译失败 - 嵌入接口时不能加
*:interface { *Reader }❌ 正确是interface { Reader } - 多个接口嵌入冲突?比如
A和B都定义了Close(),那外层接口等价于只有一份Close(),实现时写一次就行
HTTPServer 有一个 Logger),嵌入才合理;如果只是临时借用行为(比如某函数需要临时记录日志),那就该传参或闭包,而不是硬塞进结构体。


















