一尘不染

以匿名结构作为参数导出函数[在package.Func的参数中不能将值(结构{…}类型)用作结构{…}类型]

go

这是一段[play.google.org](https://play.golang.org/p/nk9HBJAMO3)可以毫无问题运行的代码:

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()上文)?
  • 以空struct作为参数的函数(也称为PrintAnonymous2()上文)可以被调用,即使它存在于另一个包中。这是特例吗?

好吧,我知道我总是可以命名struct来解决问题,我对此感到很好奇,并且想知道为什么似乎不允许这样做。


阅读 217

收藏
2020-07-02

共1个答案

一尘不染

您的匿名结构类型的字段未导出。这意味着您无法创建该结构的值,也无法为另一个包中的字段指定值。规格:复合文字:

为属于不同包的结构的非导出字段指定元素是错误的。

如果您更改结构定义以导出字段,那么它将起作用,因为所有字段都可以由其他程序包分配(请参见Siu Ching Pong -Asuka Kenji-的答案)。

空结构(也没有字段)也是如此:空结构没有字段,因此没有未导出的字段,因此您可以传递该值。

但是,您可以通过反射使用未经修改的结构(具有未导出的字段)来调用该函数。你可以得到reflect.Type的的PrintAnonymous()功能,并且可以使用Type.In()来获取reflect.Type它的第一个参数。那就是我们要传递值的匿名结构。您可以使用构造该类型的值reflect.New()。这将是一个reflect.Value,包装指向匿名结构零值的指针。抱歉,您的结构值不能包含非零值的字段(由于上述原因)。

它看起来像这样:

v := reflect.ValueOf(somepackage.PrintAnonymous)
paramt := v.Type().In(0)
v.Call([]reflect.Value{reflect.New(paramt).Elem()})

这将打印:

0:

0是的零值int,和的""空字符串string


有趣的是( 这是一个错误
,请参见下面的链接问题),通过反射,您可以使用自己的匿名结构类型的值(具有匹配的未导出字段),在这种情况下,我们可以使用除0之外的其他值。结构域:

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()函数的参数)–错误地–报告相同的包,因此它们的类型将相等:

fmt.Println(t == t2) // Prints true

注意:我认为这是一个 缺陷
:如果允许使用反射,则应该也可以不使用反射。如果在没有反射的情况下证明了编译时错误,那么使用反射会导致运行时出现恐慌。我为此打开了一个问题,您可以在此处关注它:Issue#16616。Fix当前针对Go1.8。

2020-07-02