Skip to content

Commit 2e43cbb

Browse files
committed
💪 Add post
1 parent a181b5c commit 2e43cbb

File tree

1 file changed

+53
-0
lines changed

1 file changed

+53
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
title: Go ジェネリクスでインターフェース制約を満たしつつ zero valueで初期化する
3+
date: '2025-07-21'
4+
published: '2025-07-21'
5+
---
6+
7+
例えば、proto.Message を型制約に持つgenericな関数を書こうとすると、次のような落とし穴がある:
8+
9+
```go
10+
func DecodeProtoMessage[T proto.Message](data []byte) (*T, error) {
11+
...
12+
var m T
13+
if err := proto.Unmarshal(b, m); err != nil {
14+
...
15+
}
16+
return &m, nil
17+
}
18+
```
19+
20+
このとき、`T``*Pet` のようなポインタ型を指定すると、`var m T` は nilのままとなり、`proto.Unmarshal` はpanicしてしまう。
21+
Go のジェネリクスでは、ポインタ型に対するゼロ値の `new(T)``var m T` は nilなので、non-nil前提の処理ではこうなる。
22+
23+
## 解決策:値型 T とポインタ型 *T を分離して制約する
24+
25+
そこで、以下のように値型 `T` に対するポインタ型 `*T` に制約をかけることで、安全な初期化ができる:
26+
27+
```go
28+
type ProtoMessagePtr[T any] interface {
29+
*T
30+
proto.Message
31+
}
32+
33+
func DecodeProtoMessage3[T any, P ProtoMessagePtr[T]](data []byte) (*T, error) {
34+
...
35+
var m T
36+
var p P = &m
37+
if err := proto.Unmarshal(b, p); err != nil {
38+
...
39+
}
40+
return &m, nil
41+
}
42+
```
43+
44+
`T``any` にもかかわらず、 `P` の型制約によって`*T``proto.Message` を満たすことを関数定義が要求していることがポイント。
45+
この方法は、`proto.Message` に限らず、任意のinterfaceに応用できる。
46+
47+
使用例:
48+
```go
49+
dst, err := DecodeProtoMessage[petstorev1.Pet](encodedData)
50+
```
51+
52+
このように `P` は第1型パラメータによって自明なので、関数を利用する際には `ProtoMessagePtr` を指定する必要もない。
53+
reflectionやfactoryなどを使わずに、型安全にzero valueを生成できていい感じ。

0 commit comments

Comments
 (0)