Skip to content

2.4 接口:类型断言

类型断言(Type Assertion)用于从接口类型的值里取出动态类型的具体值,或判断动态类型是否为某种类型。语法为 x.(T)x 必须是接口类型的表达式,T 可以是具体类型或接口类型。

常见用途包括:

  • anyinterface{} 中取出 intstring 等。
  • errorio.Reader 等接口值断成已知的实现类型(如 *os.File*bytes.Buffer)。

下面三种写法分别对应:失败即 panic失败不 panic(comma-ok)按类型分支(type switch)

单值断言

语法 v := x.(T)。断言成功时,v 的类型为 T,值为 x 里保存的动态值;失败则 panic

适用于你能确定动态类型一定是 T 的场景(例如同一函数内刚赋过值)。类型来自外部或不确定时,应改用下一节的 comma-ok

go
package main

import "fmt"

func main() {
	var i any = 10
	t1 := i.(int)
	fmt.Println(t1)

	fmt.Println("=====分隔线=====")

	t2 := i.(string)
	fmt.Println(t2)
}

运行结果:第一次打印 10;第二次断言失败,panic

text
10
=====分隔线=====
panic: interface conversion: interface {} is int, not string

goroutine 1 [running]:
main.main()
	.../main.go:12 +0x...
exit status 2

接口值为 nil 时,对任意 Tx.(T) 同样会 panic:

go
package main

func main() {
	var i any // nil
	var _ = i.(any)
}
text
panic: interface conversion: interface is nil, not interface {}

安全断言(comma-ok)

语法 v, ok := x.(T)。与单值形式一样,判断「不是 nil 接口动态类型为 T」。成功时 ok == truevT 类型的值;失败时 ok == falsevT 的零值不会 panic

go
package main

import "fmt"

func main() {
	var i any = 10
	t1, ok := i.(int)
	fmt.Printf("%d-%t\n", t1, ok)

	fmt.Println("=====分隔线1=====")

	t2, ok := i.(string)
	fmt.Printf("%q-%t\n", t2, ok)

	fmt.Println("=====分隔线2=====")

	var k any // nil
	t3, ok := k.(any)
	fmt.Println(t3, "-", ok)

	fmt.Println("=====分隔线3=====")
	k = 10
	t4, ok := k.(any)
	fmt.Printf("%d-%t\n", t4, ok)

	t5, ok := k.(int)
	fmt.Printf("%d-%t\n", t5, ok)
}

输出示例:

text
10-true
=====分隔线1=====
""-false
=====分隔线2=====
<nil> - false
=====分隔线3=====
10-true
10-true

第二次断言失败时,-false 前面并非没打印,而是 t2 得到 string 零值 "",长度为 0,所以看起来像「空输出」。

处理「可能是多种类型」的 any 时,应用本形式分支判断,避免直接使用 x.(T)

类型选择(type switch)

语法 switch v := x.(type)。需要在多种类型之间分支时,用 switch 搭配 x.(type),比多次写断言更清晰。

go
package main

import "fmt"

func findType(i any) {
	switch x := i.(type) {
	case int:
		fmt.Println(x, "is int")
	case string:
		fmt.Println(x, "is string")
	case nil:
		fmt.Println(x, "is nil")
	default:
		fmt.Println(x, "not type matched")
	}
}

func main() {
	findType(10)
	findType("hello")

	var k any
	findType(k)

	findType(10.23)
}

输出:

text
10 is int
hello is string
<nil> is nil
10.23 not type matched

要点:

  • 动态值为 nil 时走 case nil
  • 没有匹配的类型时走 default
  • 使用 switch v := i.(type) 时,各 case 分支里的 v 已是该分支对应类型,一般不必再断言
  • 也可以写 switch i.(type) 而不绑定 v,分支里 i 仍是接口类型,需要具体类型时还得再断言,多数情况不如 switch v := i.(type) 方便。

补充说明

1. x 必须是接口类型

断言针对的是接口表达式。不仅限于 any,例如 var r io.Reader = buf; b := r.(*bytes.Buffer) 同样合法(失败仍会 panic,不确定时应写 b, ok := r.(*bytes.Buffer))。若 x 的静态类型根本不是接口,编译报错

2. T 也可以是接口类型

此时表示:动态类型实现了 T 则断言成功,v 的静态类型为 T。动态值为 nil 时的规则与具体类型断言一致;完整规则以 Go 语言规范:Type assertions 为准。

3. 断言与「原变量类型」

断言成功后得到的是类型 T 的值;原变量 x 的声明类型仍是原来的接口类型,并没有被改掉。

4. 与「接口潜规则」

仅接收 interface{}、类型集、反射等注意点,可与 关于接口的三个「潜规则」 对照阅读。

小结

写法失败时适用
v := x.(T)panic能确定动态类型必为 T
v, ok := x.(T)ok == falsev 为零值类型不确定时的首选
switch v := x.(type)default / case nil多类型分支

参考文章