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

5 回复

这是一个特性。像你这样的导出函数也可以返回未导出的结构体(或其他类型)。

但是,当你将某个字段设为未导出时,其他包就无法直接访问它们:

type myStruct struct {
  number int     // 未导出
  Text   string  // 已导出
}

例如,main.go 不能再这样做:

b := loadme.ReturnMyStructType()
fmt.Println(b.number)

但可以这样做:

b := loadme.ReturnMyStructType()
fmt.Println(b.Text)

更多关于Golang类型推断中引用未导出类型——是Bug还是特性?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Barpfotenbaer:

var c loadme.myStruct c = loadme.ReturnMyStructType() fmt.Println(c)

这里有一篇关于这种行为的优秀文章。

ardanlabs.com

Go语言中的导出/未导出标识符

Ardan Labs

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
}

技术原理:

  1. 类型推断:当使用短变量声明(:=)时,Go编译器能够推断出变量的类型,即使这个类型在包外部是未导出的。

  2. 封装边界:Go的封装机制阻止的是直接引用未导出标识符,但不阻止通过导出函数间接使用这些类型。

  3. 类型一致性:函数签名中的类型信息在编译时是完整的,编译器能够确保类型安全。

更复杂的示例:

// 包内部
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语言设计的有意特性,它允许包作者控制类型的可见性,同时仍然能够通过导出函数提供对这些类型的操作。

回到顶部