この Codelab では、Goの標準ライブラリのひとつである reflect パッケージを学びます。

以下のゴールを達成して、Goでメタプログラミングができるようになりましょう!

reflect パッケージは、Goの標準ライブラリのひとつです。 reflect パッケージを使うと、プログラムは、自身を鏡に反射(reflection)するように、ランタイムの型や値を見ることができるようになります。

そして、型や値を見るだけだなく、ランタイムでそれらを変更することができます。

ランタイムの型の情報を取得するには reflect.TypeOf()を使用します。

取得できる型情報は Type interface を実装しています。

package main

import (
	"fmt"
	"reflect"
)

type person struct {
	name string
}

func main() {
	p := person{
		name: "にゅも太郎",
	}

	t := reflect.TypeOf(p)

	fmt.Println(t.Name()) // person
}

ランタイムの値の情報を取得するには reflect.ValueOf()を使用します。

取得できる値の情報は Value struct を実装しています。

package main

import (
	"fmt"
	"reflect"
)

type person struct {
	name string
}

func main() {
	p := &person{
		name: "にゅも太郎",
	}

	v := reflect.ValueOf(p)

	fmt.Println(v.Elem()) // {にゅも太郎}
}

ある struct A を、別の struct Bにコピーすることを考えます。 もっともシンプルな方法は、すべてのフィールドに対して代入をおこなうことです。

package main

import (
	"fmt"
)

type Person struct {
	Name string
	Age  int
}

func main() {
	p1 := Person{Name: "Alice", Age: 20}
	p2 := Person{}

	copyPerson(&p1, &p2)

	fmt.Println(p2) // {Alice 20}
}

func copyPerson(src, dst *Person) {
	dst.Name = src.Name
	dst.Age = src.Age
}

この方法には、以下のような問題があります。

reflect パッケージを使ってこの問題を解決してみましょう。

任意の型の struct をコピーできる copyStruct 関数を実装してみましょう。 雛形のコードはこちらです。

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string
	DNA  string
	Soul string `copyable:"false"`
}

type Food struct {
	Name             string
	Kind             string
	secretIngredient string
}

func main() {
	p1 := Person{Name: "Alice", DNA: "ALICE_DNA", Soul: "ALICE_SOUL"}
	p2 := Person{}
	f1 := Food{Name: "Icecream", Kind: "Sweet", secretIngredient: "Salt"}
	f2 := Food{}

	copyStruct(&p1, &p2)
	copyStruct(&f1, &f2)

	fmt.Println(p2) // {Alice 20}
	fmt.Println(f2) // {Icecream Sweet}
}

// TODO: implement
func copyStruct() {}

考え方

応用問題

フィールドが copyable:"false" の Struct Tag をもつ場合、代入をスキップしてみましょう。

reflect パッケージを使ったメタプログラミングには、ランタイムの振る舞いを変える以外にも、以下のように使うことができます。 関連のOSSのソースコードなどを読んでみましょう。