Golang类型断言问题探讨

Golang类型断言问题探讨 我正在尝试理解一个类型断言的细节。以下是示例:

type StringThing string
var a interface{} = StringThing("test")
x, ok := a.(StringThing)
fmt.Println(x, ok)	// "test true"
y, ok := a.(string)
fmt.Println(y, ok)	// " false"

以及:

type ArrayThing []interface{}
var a interface{} = ArrayThing{"one",2}
x, ok := a.(ArrayThing)
fmt.Println(x, ok)	// "[one 2] true"
y, ok := a.([]interface{})
fmt.Println(y, ok)	// "[] false"

这些代码展示了问题所在。

我不太确定为什么在这些情况下,类型断言与底层类型定义不匹配。我并不是说这里的行为不正确,我只是想知道其中的原理。

我目前的想法是,这些是类型断言,而不是类型转换,并且特定的 type 声明创建了新的、独立的类型,而不是底层类型的宏。因此,每种情况下的第二个断言都失败了。

话虽如此,断言的要点似乎是编译器可以对值做出某种假设(例如,在每种情况下都可以遍历 interface{} 数组),并且在两种情况下,这个假设似乎都是有效的。


更多关于Golang类型断言问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

搜索 covariant(协变)和 invariant(不变)类型。

interface{} 是协变的

array types 是不变的

这是一个相当深奥的集合论主题,理论会很快变得复杂。

更多关于Golang类型断言问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


以下代码:

type StringThing = string
var a interface{} = StringThing("test")
x, ok := a.(StringThing)
fmt.Println(x, ok)	// "test true"
y, ok := a.(string)
fmt.Println(y, ok)	// "test true"

type ArrayThing = []interface{}
var a interface{} = ArrayThing{"one",2}
x, ok := a.(ArrayThing)
fmt.Println(x, ok)	// "[one 2] true"
y, ok := a.([]interface{})
fmt.Println(y, ok)	// "[one 2] true"

的运行结果与我最初的预期一致。

在类型声明中使用等号不会创建一个新的、独立的类型。

在类型声明中使用等号不会创建一个新的独立类型。

再次强调,Go编译器实现了语言规范。

Go编程语言规范

类型声明

类型声明将一个标识符(类型名称)绑定到一个类型。类型声明有两种形式:别名声明和类型定义。

TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .

别名声明

别名声明将一个标识符绑定到给定的类型。

AliasDecl = identifier "=" Type .

在该标识符的作用域内,它作为该类型的别名。

你将你的类型声明从类型定义

type StringThing string

改为了别名声明

type StringThing = string

对于别名声明,StringThingstring 表示相同的类型。


类型别名声明有助于在大规模重构期间进行渐进式代码修复:提案:类型别名

mAdkins23:

我当前的想法

Go 编译器实现的是语言规范,而不是你的想法。

Go 编程语言规范

类型声明

类型定义

类型定义会创建一个新的、独特的类型,该类型具有与给定类型相同的基础类型和操作,并将一个标识符绑定到它。

TypeDef = identifier Type .

这个新类型被称为已定义类型。它不同于任何其他类型,包括创建它的原始类型。

类型断言

对于一个接口类型的表达式 x 和一个类型 T,主表达式

x.(T)

断言 x 不是 nil 并且存储在 x 中的值是 T 类型。符号 x.(T) 被称为类型断言。

更精确地说,如果 T 不是接口类型,x.(T) 断言 x 的动态类型与类型 T 完全相同。

类型同一性

两个类型要么相同,要么不同。

一个已定义类型总是不同于任何其他类型。

StringThingstring 是不同的、独立的类型。它们不是同一的。

在Go语言中,类型断言确实不是类型转换,你的理解基本正确。类型断言用于检查接口值是否持有特定的具体类型,而类型转换则是在具有相同底层类型的类型之间进行转换。以下是详细解释和示例:

1. 类型断言的工作原理

类型断言 x.(T) 检查接口值 x 是否持有类型 T。如果 T 是具体类型,断言会检查 x 的动态类型是否与 T 完全相同。如果 T 是接口类型,断言会检查 x 的动态类型是否实现了 T

2. 为什么第二个断言失败

在Go中,使用 type 定义的新类型(如 StringThingArrayThing)是独立的类型,即使它们共享相同的底层类型。类型断言要求类型完全匹配,因此:

  • StringThingstring 是不同的类型。
  • ArrayThing[]interface{} 是不同的类型。

3. 示例代码分析

示例1:StringThingstring

type StringThing string
var a interface{} = StringThing("test")
x, ok := a.(StringThing) // 成功:a 的动态类型是 StringThing
fmt.Println(x, ok)       // 输出: test true

y, ok := a.(string)      // 失败:a 的动态类型是 StringThing,不是 string
fmt.Println(y, ok)       // 输出:  false

示例2:ArrayThing[]interface{}

type ArrayThing []interface{}
var a interface{} = ArrayThing{"one", 2}
x, ok := a.(ArrayThing)    // 成功:a 的动态类型是 ArrayThing
fmt.Println(x, ok)         // 输出: [one 2] true

y, ok := a.([]interface{}) // 失败:a 的动态类型是 ArrayThing,不是 []interface{}
fmt.Println(y, ok)         // 输出: [] false

4. 如何实现类型转换

如果需要将 StringThing 转换为 string,或 ArrayThing 转换为 []interface{},必须使用显式类型转换,因为它们是不同的类型。但注意:类型转换要求两者具有相同的底层类型。

// StringThing 转换为 string
type StringThing string
var st StringThing = "test"
s := string(st) // 类型转换
fmt.Println(s)  // 输出: test

// ArrayThing 转换为 []interface{}
type ArrayThing []interface{}
at := ArrayThing{"one", 2}
slice := []interface{}(at) // 类型转换
fmt.Println(slice)         // 输出: [one 2]

5. 接口类型断言的特殊情况

如果类型断言的目标是接口类型,Go会检查动态类型是否实现了该接口。例如:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type MyReader struct{}

func (m MyReader) Read(p []byte) (n int, err error) {
    return len(p), nil
}

var a interface{} = MyReader{}
r, ok := a.(Reader) // 成功:MyReader 实现了 Reader 接口
fmt.Println(r, ok)  // 输出: {} true

总结

类型断言严格检查接口值的动态类型是否与目标类型完全相同。使用 type 定义的新类型是独立类型,即使底层类型相同,类型断言也不会将其视为相同类型。如果需要转换,必须使用显式类型转换(前提是底层类型相同)。

回到顶部