这是一段代码 play.google.org 运行没有任何问题:
package main
import (
"fmt"
)
func PrintAnonymous(v struct {
i int
s string
}) {
fmt.Printf("%d: %s\n", v.i, v.s)
}
func PrintAnonymous2(v struct{}) {
fmt.Println("Whatever")
}
func main() {
value := struct {
i int
s string
}{
0, "Hello, world!",
}
PrintAnonymous(value)
PrintAnonymous2(struct{}{})
}
但是,如果PrintAnonymous()
函数存在于另一个包中(例如,temp
),则代码将无效:
cannot use value (type struct { i int; s string })
as type struct { i int; s string } in argument to temp.PrintAnonymous
我的问题是:
PrintAnonymous()
)?PrintAnonymous2()
)。这是一个特例吗?嗯,我知道我总是可以命名struct
来解决问题,我只是对此感到好奇,并想知道为什么这似乎不允许这样做。
答案 0 :(得分:9)
您的匿名结构类型的字段未导出。这意味着您无法创建此结构的值并为另一个包中的字段指定值。 Spec: Composite literals:
为属于不同包的结构的非导出字段指定元素是错误的。
如果更改结构定义以导出字段,那么它将起作用,因为所有字段都可以由其他包分配(参见Siu Ching Pong -Asuka Kenji的回答)。
空结构(没有字段)就是这种情况:空结构没有字段,因此它没有未导出的字段,所以你可以传递一个值。
您可以通过reflection使用未修改的struct(带有未导出的字段)调用该函数。您可以获取PrintAnonymous()
函数的reflect.Type
,并且可以使用Type.In()
获取其第一个参数的reflect.Type
。这是我们想要为其传递值的匿名结构。您可以使用reflect.New()
构建该类型的值。这将是一个reflect.Value
,包含指向匿名结构的zero value的指针。抱歉,您不能使用具有非零值的字段的结构值(出于上述原因)。
这就是它的样子:
v := reflect.ValueOf(somepackage.PrintAnonymous)
paramt := v.Type().In(0)
v.Call([]reflect.Value{reflect.New(paramt).Elem()})
这将打印:
0:
0
为int
的值为零,""
的{{1}}为空字符串。
要深入了解类型系统和使用未导出字段的结构,请参阅相关问题:
<强> Identify non builtin-types using reflect 强>
的 How to clone a structure with unexported field? 强>
有趣的是(这是一个错误,请参阅下面的链接问题),使用反射,您可以使用自己的匿名结构类型的值(带有匹配的,未导出的字段),在这种情况下,我们可以使用结构字段的零值以外的值:
string
以上运行(没有恐慌):
value := struct {
i int
s string
}{
1, "Hello, world!",
}
v.Call([]reflect.Value{reflect.ValueOf(value)})
允许这样做的原因是由于编译器中的错误。请参阅以下示例代码:
1: Hello, world!
输出是:
s := struct{ i int }{2}
t := reflect.TypeOf(s)
fmt.Printf("Name: %q, PkgPath: %q\n", t.Name(), t.PkgPath())
fmt.Printf("Name: %q, PkgPath: %q\n", t.Field(0).Name, t.Field(0).PkgPath)
t2 := reflect.TypeOf(subplay.PrintAnonymous).In(0)
fmt.Printf("Name: %q, PkgPath: %q\n", t2.Name(), t2.PkgPath())
fmt.Printf("Name: %q, PkgPath: %q\n", t2.Field(0).Name, t2.Field(0).PkgPath)
正如您可以在两个匿名结构类型中看到未导出的字段Name: "", PkgPath: ""
Name: "i", PkgPath: "main"
Name: "", PkgPath: ""
Name: "i", PkgPath: "main"
(在i
包中和main
中作为somepackage
函数的参数) - 错误地报告同一个包,因此它们的类型相同:
PrintAnonymous()
注意:我认为这是缺陷:如果允许使用反射,那么这也应该可以不使用反射。如果没有反射,编译时错误是合理的,那么使用反射应该导致运行时混乱。我为此打开了一个问题,您可以在此处关注:issue #16616。 Fix目前的目标是Go 1.8。
答案 1 :(得分:5)
哦!我刚刚意识到字段名称是小写的,因此不公开!将字段名称的第一个字母更改为大写可以解决问题:
package main
import (
"temp"
)
func main() {
value := struct {
I int
S string
}{
0, "Hello, world!",
}
temp.PrintAnonymous(value)
temp.PrintAnonymous2(struct{}{})
}
package temp
import (
"fmt"
)
func PrintAnonymous(v struct{I int; S string}) {
fmt.Printf("%d: %s\n", v.I, v.S)
}
func PrintAnonymous2(v struct{}) {
fmt.Println("Whatever")
}