Экспорт функций с анонимной структурой как параметр [не может использовать значение (type struct {...}) как type struct {...} в аргументе package.Func]

Вот фрагмент кода 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 для решения проблемы, мне просто интересно, и интересно, почему кажется, что это не разрешено.

Ответы

Ответ 1

Поля вашего анонимного типа структуры не экспортируются. Это означает, что вы не можете создавать значения этой структуры и указывать значения для полей из другого пакета. Spec: Композитные литералы:

Это ошибка для указания элемента для неэкпортируемого поля структуры, принадлежащей другому пакету.

Если вы измените определение структуры для экспорта полей, оно будет работать, потому что все поля могут быть назначены другими пакетами (см. Siu Ching Pong -Asuka Kenji- answer).

Это также относится к пустой структуре (без полей): пустая структура не имеет полей, поэтому она не имеет невыполненных полей, поэтому вам разрешено передать ее значение.

Вы можете вызвать функцию с немодифицированной структурой (с невыполненными полями) через отражение. Вы можете получить reflect.Type функции PrintAnonymous(), и вы можете использовать Type.In(), чтобы получить reflect.Type своего первого параметр. Это анонимная структура, для которой мы хотим передать значение. И вы можете построить значение этого типа, используя reflect.New(). Это будет reflect.Value, заверяющий указатель на zero значение анонимной структуры. К сожалению, вы не можете иметь значение struct с полями, отличными от нуля (по причине, упомянутой выше).

Вот как это могло бы выглядеть:

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

Это напечатает:

0: 

0 - это нулевое значение для int и "" пустая строка для 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())), - сообщите об этом же пакете, и, следовательно, их тип будет быть равно:

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

Примечание. Я считаю это недостатком: если это разрешено с помощью отражения, тогда это должно быть возможно без использования отражения. Если без отражения ошибка времени компиляции оправдана, то использование отражения должно привести к панике во время выполнения. Я открыл для этого проблему, вы можете следить за ней здесь: issue # 16616. Исправление в настоящее время нацелено на Go 1.8.

Ответ 2

О! Я просто понял, что имена полей в нижнем регистре и, следовательно, не являются общедоступными! Изменение первой буквы имен полей на верхний регистр решает проблему:

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