🐙
Goでgenericsを使って関数を共通化したり便利な関数作る
やること
- 文字列や数値のポインタを返す便利な関数を作る
- generics を使って複数の型を受け取り処理を共通化した関数を作る
今更感がありますが、色々調べたことをまとめます。
文字列や数値のポインタを返す便利な関数を作る
例えば、一部のフィールドで nil を許容したいためにプリミティブ型のポインタを受け取るフィールドがあるとします。
type User struct {
ID int
Name string
Age *int
}
この構造体を使うときに以下のように数値を直接ポインタとして渡すことはできないため
func main.go() {
param := User{
ID: 1,
Name: "saitooooooo",
Age: &31,
}
}
このような T 型を許容する引数 x の関数を作って、そのままポインタを返す関数を作ります。
func Ptr[T any](x T) T {
return &x
}
使い方は以下の通りです。
func main.go() {
param := User{
ID: 1,
Name: "saitooooooo",
- Age: &31,
+ Age: Ptr(31),
}
}
ちなみに[T any](x T) *T
としないで(x any) *any
でもいいんじゃないと思ったのですが、こちらでは型が合わないためコンパイルできません。
NG例
func Ptr(x any) *any {
return &x
}
func main() {
param := User{
ID: 1,
Name: "saitooooooo",
Age: Ptr(31), // [gopls] IncompatibleAssign: cannot use Ptr(31) (value of type *any) as *int value in struct literal
}
}
複数の型を受け取り処理を共通化した関数を作る
先ほどの例で使った構造体を拡張します。
type User struct {
ID int
Name string
Age *int
}
type A struct {
User User
}
type B struct {
User User
Country *string
}
例えば、type A
もtype B
もUser構造体
を持つフィールドがあり、
-
User構造体
のAge
が nil かチェックする -
B構造体
を受け取った時はCountry
が nil かチェックする
のような処理を実装したい場合にAge
の nil チェックは、A 構造体だろうと B 構造体だろうと同じ処理を使いたい状況だとします。
そのような状況で以下のように実装するとエラーメッセージを共通化することができます。
func validateParam[T A | B](param T) error {
switch v := any(param).(type) {
case A:
if err := validateUserAge(v.User); err != nil {
return err
}
case B:
if err := validateUserAge(v.User); err != nil {
return err
}
}
return nil
}
// こちらでUser構造体のAgeをチェックする
func validateUserAge(param User) error {
if param.Age == nil {
return xerrors.Errorf("User.Ageは必須です")
}
return nil
}
ここにB構造体
の時だけCountry
の nil チェックを実装します。
func validateParam[T A | B](param T) error {
switch v := any(param).(type) {
case A:
if err := validateUserAge(v.User); err != nil {
return err
}
case B:
if err := validateUserAge(v.User); err != nil {
return err
}
+ if v.Country == nil {
+ return xerrors.Errorf("Countryは必須です: %+v", v)
+ }
}
return nil
}
これがどういう時に使えるかと言うと、
登録と更新の API で同じパラメータ+更新だけ ID が必要みたいな状況で
同じバリデーションロジックを使うことができるようになります。
Discussion