dongshang1529 2016-08-05 08:44
浏览 83
已采纳

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

Here is a piece of code <kbd>play.google.org</kbd> that runs without any problem:

package main

import (
    "fmt"
)

func PrintAnonymous(v struct {
    i int
    s string
}) {
    fmt.Printf("%d: %s
", 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{}{})
}

However, if the PrintAnonymous() function exists in another package (let's say, temp), the code will not work:

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

My question are:

  • Is there a way to call a (public) function with anonymous struct as a parameter (a.k.a. PrintAnonymous() above)?
  • A function with empty struct as a parameter (a.k.a. PrintAnonymous2() above) can be called even if it exists in another package. Is this a special case?

Well, I know that I can always name the struct to solve the problem, I'm just curious about this, and wonder why it seems that this is not allowed.

  • 写回答

2条回答 默认 最新

  • dsgk40568 2016-08-05 09:33
    关注

    The fields of your anonymous struct type are unexported. This means you cannot create values of this struct and specify values for the fields from another package. Spec: Composite literals:

    It is an error to specify an element for a non-exported field of a struct belonging to a different package.

    If you change the struct definition to export the fields, then it will work because all fields can be assigned to by other packages (see Siu Ching Pong -Asuka Kenji-'s answer).

    This is the case with the empty struct (with no fields) too: the empty struct has no fields, thus it has no unexported fields, so you're allowed to pass a value of that.

    You can call the function with unmodified struct (with unexported fields) via reflection though. You can obtain the reflect.Type of the PrintAnonymous() function, and you can use Type.In() to get the reflect.Type of its first parameter. That is the anonymous struct we want to pass a value for. And you can construct a value of that type using reflect.New(). This will be a reflect.Value, wrapping a pointer to the zero value of the anonymous struct. Sorry, you can't have a struct value with fields having non-zero values (for reason mentioned above).

    This is how it could look like:

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

    This will print:

    0: 
    

    0 is zero value for int, and "" empty string for string.

    For deeper inside into the type system and structs with unexported fields, see related questions:

    Identify non builtin-types using reflect
    How to clone a structure with unexported field?


    Interestingly (this is a bug, see linked issue below), using reflection, you can use a value of your own anonymous struct type (with matching, unexported fields), and in this case we can use values other than the zero value of the struct fields:

    value := struct {
        i int
        s string
    }{
        1, "Hello, world!",
    }
    
    v.Call([]reflect.Value{reflect.ValueOf(value)})
    

    Above runs (without panic):

    1: Hello, world!
    

    The reason why this is allowed is due to a bug in the compiler. See the example code below:

    s := struct{ i int }{2}
    
    t := reflect.TypeOf(s)
    fmt.Printf("Name: %q, PkgPath: %q
    ", t.Name(), t.PkgPath())
    fmt.Printf("Name: %q, PkgPath: %q
    ", t.Field(0).Name, t.Field(0).PkgPath)
    
    t2 := reflect.TypeOf(subplay.PrintAnonymous).In(0)
    fmt.Printf("Name: %q, PkgPath: %q
    ", t2.Name(), t2.PkgPath())
    fmt.Printf("Name: %q, PkgPath: %q
    ", t2.Field(0).Name, t2.Field(0).PkgPath)
    

    Output is:

    Name: "", PkgPath: ""
    Name: "i", PkgPath: "main"
    Name: "", PkgPath: ""
    Name: "i", PkgPath: "main"
    

    As you can see the unexported field i in both anonymous struct types (in main package and in somepackage as parameter to PrintAnonymous() function) –falsely– report the same package, and thus their type will be equal:

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

    Note: I consider this a flaw: if this is allowed using reflection, then this should be possible without using reflection too. If without reflection the compile-time error is justified, then using reflection should result in runtime panic. I opened an issue for this, you can follow it here: issue #16616. Fix currently aims Go 1.8.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 安装svn网络有问题怎么办
  • ¥15 Python爬取指定微博话题下的内容,保存为txt
  • ¥15 vue2登录调用后端接口如何实现
  • ¥65 永磁型步进电机PID算法
  • ¥15 sqlite 附加(attach database)加密数据库时,返回26是什么原因呢?
  • ¥88 找成都本地经验丰富懂小程序开发的技术大咖
  • ¥15 如何处理复杂数据表格的除法运算
  • ¥15 如何用stc8h1k08的片子做485数据透传的功能?(关键词-串口)
  • ¥15 有兄弟姐妹会用word插图功能制作类似citespace的图片吗?
  • ¥15 latex怎么处理论文引理引用参考文献