Golang类型推断中引用未导出类型——是Bug还是特性?
Golang类型推断中引用未导出类型——是Bug还是特性? 大家好,
假设我们使用以下简单的包…
package loadme
type myStruct struct {
Number int
Text string
}
func ReturnMyStructType() myStruct {
var a myStruct = myStruct{123, "abc"}
return a
}
那么,这段代码是有效的,但它本不应该有效!
import (
"fmt"
"myProjekt/loadme"
)
func main() {
b := loadme.ReturnMyStructType()
fmt.Println(b)
}
在我看来,这个例子应该是无效的,因为b现在获得了未导出的类型 loadme.myStruct。但请验证一下,它确实能运行!
请与这段无效代码进行比较
// ./main.go:13:8: cannot refer to unexported name loadme.myStruct
var c loadme.myStruct
c = loadme.ReturnMyStructType()
fmt.Println(c)
这种行为是bug还是特性?(我使用的是go1.11.4)
更多关于Golang类型推断中引用未导出类型——是Bug还是特性?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
Barpfotenbaer:
var c loadme.myStruct c = loadme.ReturnMyStructType() fmt.Println(c)
这里有一篇关于这种行为的优秀文章。
Go语言中的导出/未导出标识符

Ardan Labs受到小型初创公司和财富500强企业的信赖,为其培训工程师并开发商业软件解决方案和应用程序。
关键词是:标识符。
导出机制的核心在于你能否直接访问来自另一个包的标识符。这完全取决于标识符本身。它并不阻止间接使用这些标识符(例如打印未导出的结构体等),因为你的导出函数可以返回它(因为你的函数有权访问这些标识符)。
以下是有效的,因为你没有访问任何未导出的标识符:
// 这里 Go 推断变量 `b` 的类型为 `loadme.myStruct`,
// 并将其加载到变量 `b` 中。
//
// 但你无法通过其"名称"(其标识符:myStruct)"访问" myStruct
b := loadme.ReturnMyStructType()
以下是无效的,因为你试图访问一个未导出的标识符:myStruct:
var b loadme.myStruct
感谢你的回答!不过,我不认为这种不一致的行为是一个特性。因为你可以有意地通过将结构体的首字母大写来实现相同的效果……
type MyStruct struct {
number int // 仍然未导出
Text string // 已导出
}
……但区别在于,在这种情况下,以下代码是可行且有效的:
import (
"fmt"
"myProjekt/loadme"
)
func main() {
var b loadme.MyStruct
b = loadme.ReturnMyStructType()
fmt.Println(b)
}
然而
import (
"fmt"
"myProjekt/loadme"
)
func main() {
var b loadme.myStruct
b = loadme.ReturnMyStructType()
fmt.Println(b)
}
必须是无效的!这就是为什么
import (
"fmt"
"myProjekt/loadme"
)
func main() {
b: = loadme.ReturnMyStructType()
fmt.Println(b)
}
也应该是无效的,对于……
type myStruct struct {
number int // 仍然未导出
Text string // 已导出
}
这是一个Go语言的特性,不是bug。这种行为是由Go的类型系统设计决定的。
在Go语言中,未导出的类型(以小写字母开头的类型)在其定义的包外部无法直接引用,但可以通过导出的函数返回值来使用。这是Go封装机制的一部分。
代码示例说明:
// 包 loadme
package loadme
type myStruct struct { // 未导出类型
Number int
Text string
}
// 导出函数返回未导出类型
func ReturnMyStructType() myStruct {
return myStruct{123, "abc"}
}
// 导出函数返回未导出类型的指针
func ReturnMyStructPtr() *myStruct {
return &myStruct{456, "def"}
}
// 主包
package main
import (
"fmt"
"myProjekt/loadme"
)
func main() {
// 有效:通过导出函数获取未导出类型的值
b := loadme.ReturnMyStructType()
fmt.Println(b.Number, b.Text) // 可以访问导出字段
// 有效:通过导出函数获取未导出类型的指针
ptr := loadme.ReturnMyStructPtr()
fmt.Println(ptr.Number, ptr.Text)
// 无效:直接引用未导出类型
// var c loadme.myStruct // 编译错误
// 有效:类型推断允许使用未导出类型
d := loadme.ReturnMyStructType()
fmt.Printf("%T\n", d) // 输出: loadme.myStruct
}
技术原理:
-
类型推断:当使用短变量声明(
:=)时,Go编译器能够推断出变量的类型,即使这个类型在包外部是未导出的。 -
封装边界:Go的封装机制阻止的是直接引用未导出标识符,但不阻止通过导出函数间接使用这些类型。
-
类型一致性:函数签名中的类型信息在编译时是完整的,编译器能够确保类型安全。
更复杂的示例:
// 包内部
package mypkg
type internalType struct {
Value int
}
func NewInternal() internalType {
return internalType{Value: 42}
}
func ProcessData(data internalType) int {
return data.Value * 2
}
// 主包中使用
package main
import "mypkg"
func main() {
// 有效:通过类型推断使用未导出类型
data := mypkg.NewInternal()
result := mypkg.ProcessData(data)
println(result) // 输出: 84
// 但仍然不能直接操作类型
// data.Value = 100 // 如果Value字段未导出,则不能访问
}
这种行为是Go语言设计的有意特性,它允许包作者控制类型的可见性,同时仍然能够通过导出函数提供对这些类型的操作。


