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

Siu*_*ji- 16 struct anonymous-types go

这是一段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{}{})
}
Run Code Online (Sandbox Code Playgroud)

但是,如果该PrintAnonymous()函数存在于另一个包中(比方说temp),代码将不起作用:

cannot use value (type struct { i int; s string })
as type struct { i int; s string } in argument to temp.PrintAnonymous
Run Code Online (Sandbox Code Playgroud)

我的问题是:

  • 有没有办法用匿名结构作为参数调用(公共)函数(又名PrintAnonymous()上面)?
  • PrintAnonymous2()即使一个函数存在于另一个包中,也可以调用具有空结构作为参数(也就是上面的参数)的函数.这是一个特例吗?

嗯,我知道我总是可以命名struct解决问题,我只是对此感到好奇,并想知道为什么看起来这是不允许的.

icz*_*cza 9

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

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

如果您更改结构定义以导出字段,那么它将起作用,因为所有字段都可以由其他包分配(请参阅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()})
Run Code Online (Sandbox Code Playgroud)

这将打印:

0: 
Run Code Online (Sandbox Code Playgroud)

0是零值int,""空字符串为string.

有关更深入到类型系统内部和使用未导出字段的结构,请参阅相关问题:

使用反射识别非内置类型
如何使用未导出的字段克隆结构?


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

value := struct {
    i int
    s string
}{
    1, "Hello, world!",
}

v.Call([]reflect.Value{reflect.ValueOf(value)})
Run Code Online (Sandbox Code Playgroud)

以上运行(没有恐慌):

1: Hello, world!
Run Code Online (Sandbox Code Playgroud)

允许这样做的原因是由于编译器中的错误.请参阅下面的示例代码:

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)
Run Code Online (Sandbox Code Playgroud)

输出是:

Name: "", PkgPath: ""
Name: "i", PkgPath: "main"
Name: "", PkgPath: ""
Name: "i", PkgPath: "main"
Run Code Online (Sandbox Code Playgroud)

正如你可以i在匿名结构类型(在main包中和在函数中somepackage作为参数PrintAnonymous())中看到未导出的字段 - 错误地 - 报告相同的包,因此它们的类型将是相同的:

fmt.Println(t == t2) // Prints true
Run Code Online (Sandbox Code Playgroud)

注意:我认为这是一个缺陷:如果允许使用反射,那么这应该是可能的,而不使用反射.如果没有反射,编译时错误是合理的,那么使用反射应该导致运行时混乱.我为此开了一个问题,你可以在这里关注它:问题#16616.修复目前的目标是Go 1.8.


Siu*_*ji- 6

哦! 我只是意识到字段名称是小写的,因此不是公开的!将字段名称的首字母更改为大写即可解决此问题:

package main

import (
    "temp"
)

func main() {
    value := struct {
        I int
        S string
    }{
        0, "Hello, world!",
    }
    temp.PrintAnonymous(value)
    temp.PrintAnonymous2(struct{}{})
}
Run Code Online (Sandbox Code Playgroud)
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")
}
Run Code Online (Sandbox Code Playgroud)