Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exporting functions with anonymous struct as a parameter [cannot use value (type struct {...}) as type struct {...} in argument to package.Func]

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

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{}{})
}

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.

like image 579
Siu Ching Pong -Asuka Kenji- Avatar asked Aug 05 '16 08:08

Siu Ching Pong -Asuka Kenji-


2 Answers

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\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)

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.

like image 66
icza Avatar answered Sep 29 '22 16:09

icza


Oh! I just realized that the field names are in lowercase, and thus not public! Changing the first letter of the field names to uppercase solves the problem:

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")
}
like image 6
Siu Ching Pong -Asuka Kenji- Avatar answered Sep 29 '22 16:09

Siu Ching Pong -Asuka Kenji-