断言 T 要求接口中存储的必须是 T 类型,而非 T 值;json.Unmarshal 默认填充值类型,故 v.(*T) 必失败,应先断言 T 再取地址或直接解码到变量。

断言 *T 必须接口里存的是指针,不是值
Go 的类型断言是严格匹配底层存储类型的:接口里存了 MyStruct{}(值),你就不能用 .(MyStruct) 以外的类型去断;同理,存了 &MyStruct{}(指针),断言目标必须是 .(*MyStruct)。常见错误就是传入 JSON 解析后的 interface{},然后直接写 v.(*MyStruct)——但 json.Unmarshal 默认填充值类型,不是指针,所以 ok 一定为 false。
- 确认原始赋值:检查是不是用了
var s MyStruct; json.Unmarshal(data, &s),而不是json.Unmarshal(data, &v)后再从v断言 - 若必须走
interface{}中间态,先断言值类型再取地址:if val, ok := i.(MyStruct); ok { ptr := &val }(注意:ptr是val的地址,val是副本) - 别依赖
reflect.Value.Addr()强转:如果原始值来自map[string]interface{}或json.RawMessage,reflect.Value不可寻址,调用.Addr()会 panic
json.Unmarshal 后的 interface{} 为什么不能直接断言为 *T
json.Unmarshal 对 interface{} 参数的行为是确定的:它只填充具体值(T),不分配堆内存、不返回指针(*T)。这不是 bug,是契约。所以 var v interface{}; json.Unmarshal(data, &v); p := v.(*MyStruct) 必然 panic。
- 推荐做法:绕过
interface{},直接解到目标变量:var s MyStruct; json.Unmarshal(data, &s) - 次选做法:先断言为值类型,再显式取地址(仅限必须保留
interface{}的场景) - 警惕
json.RawMessage:它本质是[]byte,不是结构体,反复对它做.(MyStruct)断言毫无意义,得先json.Unmarshal一次
方法集影响断言目标的选择
Go 中 T 和 *T 的方法集不同:*T 能调用接收者为 T 或 *T 的方法,而 T 只能调用接收者为 T 的方法。如果你要调用一个指针接收者方法(比如 (*MyStruct).Save()),却只断言出 MyStruct 值类型,那 val.Save() 编译不过。
- 看接口定义时绑定的方法:如果接口方法签名要求指针接收者,那么实现该接口的实例通常也应是
*T - 从
container/list或 RPC 返回值中取元素时,若原始存入的是*MyStruct,断言就必须用.(*MyStruct),否则字段可读但方法不可用 - 不确定时,优先断言指针类型(
.(*T)),失败后再退到值类型(.(T))
单值断言 v := i.(*T) 为什么危险
单值断言不带 ok 检查,一旦接口里存的不是 *T 类型(比如是 nil、T 值、或别的指针类型),程序立即 panic。这不是偶然错误,而是 Go 的设计选择——强制你处理类型不匹配。
立即学习“go语言免费学习笔记(深入)”;
- 永远优先用双值形式:
v, ok := i.(*T) - 断言失败后不要忽略
ok == false:至少记录日志或跳过,避免静默错误扩散 - 在
type switch中用i.(type)更安全,尤其当要处理多种可能类型时
*map[string]interface{},断言成 *MyStruct 就是错的——中间多了一层间接性,类型系统根本不会帮你“穿透”。


















