GoLang: свойство struct access по имени

Вот простая программа, которая не работает:

package main
import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    fmt.Println(getProperty(&v, "X"))
}

func getProperty(v *Vertex, property string) (string) {
    return v[property]
}

Ошибка: prog.go:18: invalid operation: v[property] (index of type *Vertex)

Я хочу получить доступ к свойству Vertex X, используя его имя. Если я выполняю v.X, он работает, но v["X"] не работает.

Может кто-нибудь сказать мне, как сделать эту работу?

Ответ 1

В большинстве случаев этот тип динамического поиска не нужен. Он неэффективен по сравнению с прямым доступом (компилятор знает смещение X-поля в структуре Vertex, он может скомпилировать v.X для одной машинной команды, тогда как для динамического поиска потребуется какая-то реализация хеш-таблицы или аналогичная). Он также препятствует статической типизации: компилятор не имеет возможности проверить, что вы не пытаетесь получить доступ к неизвестным полям динамически, и он не может знать, каким должен быть результирующий тип.

Но... язык предоставляет reflect модуль в редкие моменты, когда вам это нужно.

package main

import "fmt"
import "reflect"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    fmt.Println(getField(&v, "X"))
}

func getField(v *Vertex, field string) int {
    r := reflect.ValueOf(v)
    f := reflect.Indirect(r).FieldByName(field)
    return int(f.Int())
}

Здесь нет ошибок, поэтому вы получите панику, если вы попросите не существовавшее поле, или поле не относится к типу int. Подробнее о документации для отражения.

Ответ 2

У Yuo теперь есть проект oleiade/reflection, который позволяет вам получать/устанавливать поля по значению struct или указателям.
Это делает использование reflect package менее сложным.

s := MyStruct {
    FirstField: "first value",
    SecondField: 2,
    ThirdField: "third value",
}

fieldsToExtract := []string{"FirstField", "ThirdField"}

for _, fieldName := range fieldsToExtract {
    value, err := reflections.GetField(s, fieldName)
    DoWhatEverWithThatValue(value)
}


// In order to be able to set the structure values,
// a pointer to it has to be passed to it.
_ := reflections.SetField(&s, "FirstField", "new value")

// If you try to set a field value using the wrong type,
// an error will be returned
err := reflection.SetField(&s, "FirstField", 123)  // err != nil