Golang中为什么非内部包可以暴露内部实现?

Golang中为什么非内部包可以暴露内部实现? 在一个包含内部包和非内部包的Go模块中,非内部包可以暴露内部包的内容,例如内部包的结构体。当我遇到这种行为时,我原以为这是一个bug,但在cmd/go: Contents of internal package can be exposed through non-internal package · Issue #71894 · golang/go · GitHub中被确认这是有意为之的。

下面是一个例子,其中非内部函数 Foo 可以暴露内部结构体 Bar

$ cat foo/internal/bar/bar.go
package bar

type Bar struct {
        X int
}

$ cat foo/foo.go
package foo

import "foo/internal/bar"

func Foo() bar.Bar {
        return bar.Bar{X: 42}
}

当在另一个Go模块中使用 Foo 的返回值时,编译器对此没有报错。为什么会这样?这是否属于Go作者为我们提供的“一把锋利的刀”用于特殊情况,并假设我们开发者知道不去滥用它的情况之一?


更多关于Golang中为什么非内部包可以暴露内部实现?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

嗯,我还没从这个角度考虑过,感谢提供示例。

更多关于Golang中为什么非内部包可以暴露内部实现?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Go 有一个模型,即所有以大写字母开头的内容都会被导出。无论你是否将你的包命名为 internal。如果你不想让其他人访问内部实现,只保留 API 并将所有内容设为非导出即可。

你的问题很奇怪,这类似于:

type b struct {
}

func FuncA() *b {
	return &b{}
}

不要局限于特性。闭包只是为了将某些东西隔离起来,使其不能被外部调用,但这并不意味着被抛出的东西不能被使用。

我不明白。一个没有任何导出的内部包是完全无用的,因为无法与之交互。

你是否知道,internal 在包路径中具有特殊含义?请参阅 go 命令 - cmd/go - Go 包

是的,我知道。我想你需要再读一遍这篇文章。它明确指出,internal 目录下的包可以被任何根目录位于该 internal 目录之上的包导入。在你的示例中,你正是这样做的。这意味着,如果你 internal 目录下的内容是导出的,那么你就可以访问它们。即使在 GitHub 的 issue 里,也有很多关于 internal 名称误解的讨论链接。cmd/go: modules: exposure of internal packages · Issue #30569 · golang/go · GitHub

抱歉,我想我们一开始就有些误会。我很清楚 foo 包应该能够使用 foo/internal/bar 包。这是设计如此,工作正常。

让我感到惊讶的是,我竟然能够在另一个 Go 模块中使用 foo 模块的内部包的 Bar 结构体。例如,在一个不同的 Go 模块中,我能够访问这个内部结构体的字段:

$ cat baz/baz.go
package main

import "fmt"
import "foo"

func main() {
        fmt.Println(foo.Foo().X)
}

$ cd baz && go run .
42

这是 Go 语言内部包机制的一个设计特性,不是 bug。内部包(internal/)的访问限制是基于导入路径的,而不是基于类型暴露的。当一个非内部包导出了内部包的类型时,该类型可以被外部模块使用,因为外部模块是通过非内部包的公共 API 间接访问的。

具体来说:

  1. 内部包只能被与它位于同一父目录下的包导入。
  2. 但当内部包的类型通过非内部包的公共函数或返回值暴露时,外部代码可以正常使用这些类型,因为外部代码导入的是非内部包,而不是直接导入内部包。

示例:

// 模块: example.com/foo
// foo/internal/bar/bar.go
package bar

type Bar struct {
    X int
}

// foo/foo.go
package foo

import "example.com/foo/internal/bar"

// 公共函数暴露内部类型
func NewBar() bar.Bar {
    return bar.Bar{X: 42}
}

// 外部模块使用
package main

import "example.com/foo"

func main() {
    b := foo.NewBar() // 允许:通过 foo 包间接使用 bar.Bar
    _ = b.X
}

这种设计允许模块在保持内部包结构私有的同时,有选择地暴露内部实现。例如,当需要将内部类型作为公共 API 的一部分返回时(如工厂函数返回内部实现),但又不希望外部直接导入内部包。

这是 Go 团队有意为之的行为,在 issue 中已有明确说明。开发者应谨慎使用此特性,避免意外暴露过多内部细节。

回到顶部