接口赋值必须传 &v 而非 v,因方法集仅由接收者类型决定:指针接收者方法只属于 *t,值接收者方法才属于 t;接口变量不自动取址,需显式传 &v 才能匹配。

接口赋值时为什么必须传 &v 而不是 v
因为接口是否能接收某个值,取决于该值的**方法集**是否完整匹配接口定义。如果接口方法用的是指针接收者(如 func (d *Dog) Speak()),那么只有 *Dog 类型才拥有这个方法;Dog 值本身的方法集里没有它。
- 编译器允许你对可寻址的
Dog变量直接调用d.Speak()(自动取地址),但这只是语法糖,不改变方法集归属 - 接口变量存储的是「类型 + 值」二元组,它不会帮你做隐式取地址——你必须显式传
&dog - 常见错误:
s := Speaker(dog)编译失败,提示Dog does not implement Speaker - 标准库绝大多数接口(如
json.Unmarshaler、sql.Scanner)都要求指针实现,这是惯例,不是偶然
函数参数该收 *T 还是 T?看三个硬指标
别凭感觉,盯住这三点就能快速决策:
-
是否要修改原始值:比如
user.SetEmail()必须改字段,就得收*User -
结构体大小:超过 128 字节建议测一下逃逸分析;像
[1024]byte这种大结构体,传值拷贝开销明显,优先指针 -
一致性:只要有一个方法用了
*T接收者,其他方法也统一用*T,否则T和*T方法集分裂,使用者极易踩坑 - 基础类型(
int、string)、小结构体(如type Point struct{X,Y int})直接传值更安全,避免 nil 检查负担
从接口变量安全取回原始指针的唯一方式
接口变量内部藏的是动态类型和值,想还原成具体指针,只能靠类型断言,且必须带 ok 判断:
- 错误写法:
u := i.(*User)—— 若i实际存的是User值或别的类型,直接 panic - 正确写法:
if u, ok := i.(*User); ok { ... }——ok为 false 时u是*User的零值(即nil),不会崩溃 - 别试图用反射绕过:比如先
reflect.ValueOf(i).Interface()再转,既冗余又易错,纯属画蛇添足 - 如果不确定底层是值还是指针,先用
reflect.TypeOf(i).Kind()检查,但生产代码应靠设计约束,而非运行时试探
为什么绝对不要定义 *MyInterface
*MyInterface 是个危险信号,Go 不支持这种用法,也不需要它:
立即学习“go语言免费学习笔记(深入)”;
- 接口变量本身已经是一种间接引用机制,它内部天然持有指向具体类型的指针(如果是大结构体或指针接收者实现)
- 定义
type A struct{ I *MyInterface }后,a.I.MyMethod()会报错type *MyInterface does not have method MyMethod,因为*MyInterface不是接口类型,它只是个指向接口变量的指针 - 真正要共享/可变的是底层实现类型,不是接口本身——所以让
*Dog实现Speaker,而不是让*Speaker存东西 - 如果你看到别人写了
*io.Reader或类似签名,基本可以判定是误解了 Go 的接口模型
Dog 值临时升级成 *Dog 来满足接口——它要么满足,要么不满足。设计阶段就定好接收者类型,比后期各种断言和转换省心得多。










