blog.syfm

徒然なるままに考えていることなどを書いていくブログ

ある構造体型の変数の値をもう一方の変数にコピーするライブラリを書いた

書いたもの 👇

github.com

使用例

type Foo struct {
  Hoge string
  Fuga string
  piyo string
}

v1 := Foo{Hoge: "dummy", Fuga: "FUGA", piyo: "dummy"}
v2 := Foo{Hoge: "HOGE"}
ires, _ := mapstruct.Map(v1, v2)
res := ires.(Foo)

fmt.Println("%#v", res) // main.Foo{Hoge:"HOGE", Fuga:"FUGA", piyo:""}

こんな感じで、第 2 引数の構造体の各値を第 1 引数の構造体の対応するフィールドへ適用したものが interface{} 型として返ってくる。
以下のような条件の場合、そのフィールドは第 1 引数へ適用されない

  • 非公開なフィールド
  • 値がゼロ値

なので、上記の例のように、第 1 引数の Fuga フィールドに非ゼロ値が入っていて、第 2 引数の Fuga フィールドにはゼロ値が入っている場合、第 2 引数の値は適用されずに、そのまま第 1 引数の値が使われる。
どちらのフィールドも非ゼロ値の場合、第 2 引数の値が使われる。

何に使うか?

このライブラリの使用例としては、なにか設定値を表す構造体があり、アプリケーションが Git のようにローカルとグローバルの設定値を持てる場合などに使える。
例えば、以下のような設定値を表す構造体があるとする。

type Core struct {
  Editor string
}

type User struct {
  Name string
  EMail string
}

type Config struct {
  Core *Core
  User *User
}

ここで、ローカル値を優先しつつ、ローカルで何も指定していない項目はグローバルで設定している値を使いたい。
この時に mapstruct を使う。

localConfig := &Config{
  User: &User{
    Name: "ktr",
    EMail: "ktr@example.com",
  },
}

globalConfig := &Config{
  User: &User{
    Name:  "dummy",
    EMail: "dummy@example.com",
  },
  Core: &Core{
    Editor: "/usr/bin/vim",
  },
}

ic, _ := mapstruct.Map(globalConfig, localConfig)
config := ic.(*Config)
pp.Println(config)

すると以下のような構造体が返ってくることがわかる。

&main.Config{
  Core: &main.Core{
    Editor: "/usr/bin/vim",
  },
  User: &main.User{
    Name:  "ktr",
    EMail: "ktr@example.com",
  },
}

ちゃんと両方の値が適用されてて便利。
実際にこのライブラリは Evans でも使っている。(というかそのためにライブラリを書いた)

github.com

このライブラリがやっていることを手動でやろうとすると、どうしても設定値が増えたときなどにバグを生みやすいのでこれを使うことでそういったことを防ぐことができると思う。