如何访问未导出的包私有变量

huangapple go评论83阅读模式
英文:

How to access unexported package private variable

问题

像下面这样的代码

package internal

var internalVar = "foobar"

我如何从另一个包(比如package main)访问这个internalVar

internal包位于第三方库中,我的应用程序需要使用一些私有信息,原始作者不愿意将其公开为公共API。

英文:

Code like below

package internal

var internalVar = "foobar"

How can i access this internalVar from another package like package main?

internal package is placed in a 3rd party library, and my application needs to use some private info which original author is not willing to expose as a public API.

答案1

得分: 12

正如 @mkopriva 提到的,不应该在包外部访问内部变量。Go语言之所以这样做是为了强制限制可访问性,如果内部变量没有被导出,就不应该被访问。再次强调:不要这样做,这是不好的Go语言编程实践,我们不喜欢这样做。当你需要在包外部访问变量时,始终将其导出。

尽管上述免责声明非常重要,但你仍然有办法访问内部变量:指针、汇编和linkname。我将解释后者,因为它是最简单的方法:

Go编译器有一个很棒的指令叫做 //go:linkname。它可以在不同的包之间链接变量和函数。根据文档的描述:

> //go:linkname localname [importpath.name]
>
> 这个特殊指令不适用于其后的Go代码。相反,//go:linkname 指令告诉编译器使用“importpath.name”作为源代码中声明为“localname”的变量或函数的目标文件符号名。如果省略了“importpath.name”参数,该指令将使用符号的默认目标文件符号名,并且只会使该符号对其他包可访问。由于该指令可能破坏类型系统和包的模块化性,它只在导入了 "unsafe" 包的文件中启用。

这意味着你可以使用它来访问原本未导出的函数和变量,像这样:

main.go

package main

import (
	"temp/test-access-internal/internal"

	_ "unsafe"
)

//go:linkname message temp/test-access-internal/internal.message
var message string

func main() {
	message = "abc"
	println(message)
	internal.SayHello()
}

internal/internal.go

package internal

var message string = "Hello!"

func SayHello() {
	println(message)
}

你会发现输出结果符合我们覆盖的 "abc" 值。

除非你真的非常需要修改某些东西,否则不要这样做。

英文:

As @mkopriva mentioned, internal variables should not be accessed outside their packages at all. Go explicitly does that as a way to enforce accessibility and if the internal var is not exported, then it shouldn't be accessed. Again: don't do this, it's bad Go and we don't like it. Always export your variables when you need to access them outside your packages.

That huge disclaimer above being said, there are ways on how you can access internal variables: pointers, assembly and linkname. I'll explain the later since it's the easiest one:

Go compiler has this nifty directive called //go:linkname. It basically links variables/functions between different packages. From the documentation:

> //go:linkname localname [importpath.name]
>
> This special directive does not apply to the Go code that follows it. Instead, the //go:linkname directive instructs the compiler to use “importpath.name” as the object file symbol name for the variable or function declared as “localname” in the source code. If the “importpath.name” argument is omitted, the directive uses the symbol's default object file symbol name and only has the effect of making the symbol accessible to other packages. Because this directive can subvert the type system and package modularity, it is only enabled in files that have imported "unsafe".

That means that you can use it to access otherwise unexported functions and variables, with something like this:

main.go

package main

import (
	"temp/test-access-internal/internal"

	_ "unsafe"
)

//go:linkname message temp/test-access-internal/internal.message
var message string

func main() {
	message = "abc"
	println(message)
	internal.SayHello()
}

internal/internal.go

package internal

var message string = "Hello!"

func SayHello() {
	println(message)
}

You will see that the output respects the "abc" value we've overwritten.

Don't do this unless you really, really needs to monkey patch something.

huangapple
  • 本文由 发表于 2021年7月18日 15:33:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/68426991.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定