x.(T):从接口中取出值
接口值隐藏了它所持有的具体类型。类型断言 x.(T) 用来把它取回来。
输出:
gopher 6
0 false
string of length 6
x 必须是接口类型。对具体类型做断言无法编译:invalid operation: s (variable of type string) is not an interface。
单返回值形式会 panic
断言错误时,单返回值形式会 panic,信息中会写明两个类型:
输出:
recovered: interface conversion: interface {} is int, not string
只有当出现其他类型属于程序错误、你希望程序直接崩溃时,才用单返回值形式。其他情况都用 comma-ok。失败时,comma-ok 返回 T 的零值和 false。
对 nil 接口做断言也会失败:var v any; v.(string) 会 panic,信息为 interface conversion: interface {} is nil, not string,而 comma-ok 形式返回 "", false。
断言为另一个接口
T 也可以是接口类型。这时只要动态值实现了 T,断言就会成功,结果保留同一个动态值。标准库就是用这种方式检查可选能力的。
io.Copy 会检查它的数据源是否实现了 io.WriterTo,如果实现了就使用它。fmt 会检查 Stringer 和 error。这种模式让小接口保持小巧,调用方同时还能受益于功能更丰富的实现。
类型 switch
当一个值可能是几种类型之一时,类型 switch 可以替代一连串的断言。x.(type) 只能在 switch 内部使用。
输出:
hi
integer 7
integer 8
3.14
nil
error: boom
unhandled []int
值得了解的规则:
- 在只有一个类型的 case 中,
x就是那个类型。在列出多个类型的 case 和default中,x是原接口的类型(这里是any)。 - case 按顺序检查。一个值可能同时满足多个接口,所以把更具体的接口放在更宽泛的接口之前。
case nil只匹配 nil 接口,不匹配持有 nil 指针的接口。- 类型 switch 中没有
fallthrough。
errors.As:针对被包装错误的断言
错误常常会被加上上下文后包装起来:fmt.Errorf("load config: %w", err)。直接的类型断言只看最外层的错误,会漏掉里面的那个。errors.As 会沿着包装链查找。
输出:
type assertion finds it: false
errors.As finds it: open /no/such/file
只有在确定错误没有被包装时才对错误使用类型断言,而实际上这种情况几乎没有。自定义错误一节介绍了 errors.As、errors.Is 以及如何定义自己的错误类型。
断言、转换与泛型
| 你有 | 你想要 | 用法 |
|---|---|---|
int | float64 | 转换:float64(n) |
存着 int 的 any | 那个 int | 断言:v.(int) |
可能是几种类型之一的 any | 按类型分支 | 类型 switch |
可能被包装过的 error | 某个特定的错误类型 | errors.As |
| 适用于多种类型的函数 | 编译时的类型安全 | 泛型 |
满是 any 和类型 switch 的代码,往往说明用泛型或合适的接口能更好地表达意图,并在编译时发现错误。
常见错误
- 对不可信的数据使用会 panic 的形式。 解码后的 JSON、类型为
any的 map 值、插件的输入:一律用 comma-ok。 - 断言成了错误的数值类型。 JSON 数字解码到
any时是float64,所以对它们执行v.(int)会失败。 - 存的是指针,却断言成值类型。 如果接口中存的是
*User,那么v.(User)会失败。请断言v.(*User)。 - 对错误使用类型断言。 请用
errors.As。
常见问题
Go 中的类型断言是什么?
形如 x.(T) 的表达式,其中 x 是一个接口值。如果 T 是具体类型,它把 x 中存储的值以 T 类型返回。如果 T 是接口类型,它检查存储的值是否也实现了 T。单返回值形式在检查失败时会 panic;双返回值形式 v, ok := x.(T) 则通过 ok 报告结果。
Go 中如何检查接口值的类型?
用类型 switch:switch v := x.(type) { case int: ...; case string: ...; default: ... }。在每个 case 中,v 就是该 case 的类型。只判断一种类型时,comma-ok 断言 s, ok := x.(string) 更简短。调试时可以用 fmt.Printf("%T", x) 打印类型名。
Go 中类型断言和类型转换有什么区别?
转换 T(x) 把一种类型的值变成另一种类型,例如 float64(n);是否允许由编译器决定,而且在运行时不会失败。断言 x.(T) 只作用于接口值,在运行时检查其中存储的是什么类型。对于存着 int 的 any,不能写 int(x),而要写 x.(int)。
应该用类型断言来检查错误类型吗?
不应该。请用 errors.As(err, &target)。直接断言 err.(*MyError) 只看最外层的错误,所以一旦错误被 fmt.Errorf("...: %w", err) 包装,它就会失败。errors.As 会沿着包装链逐层查找。