Menu

Golang 类型断言:x.(T)、comma-ok 与类型 switch

类型断言用来从接口中取出具体的值。学习 x.(T)、永远不会 panic 的 comma-ok 形式、断言为另一个接口、类型 switch,以及处理被包装错误的 errors.As。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

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 以及如何定义自己的错误类型。

断言、转换与泛型

你有你想要用法
intfloat64转换: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 会沿着包装链逐层查找。

Coddy 编程语言插图

和 Coddy 一起学编程

开始