Skip to content

Commit 0ccb98f

Browse files
committed
deploy: 2e43cbb
1 parent db16a23 commit 0ccb98f

File tree

3 files changed

+98
-0
lines changed

3 files changed

+98
-0
lines changed

posts.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ <h1>Posts</h1>
2222
<div role="main">
2323
<article>
2424
<ul>
25+
<li>2025-07-21: <a
26+
href="./posts/interface_constraint_zero_init_in_go.html">Go
27+
ジェネリクスでインターフェース制約を満たしつつ zero
28+
valueで初期化する</a></li>
2529
<li>2025-04-28: <a
2630
href="./posts/trying-cline.html">Clineを試してわかったこと</a></li>
2731
<li>2025-04-21: <a
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<!DOCTYPE html>
2+
<html lang="ja">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
6+
<meta name="author" content="Tomochika Hara">
7+
<title>Go ジェネリクスでインターフェース制約を満たしつつ zero
8+
valueで初期化する - thara.dev</title>
9+
<link rel="stylesheet" type="text/css" href="https://thara.dev/css/neat.css">
10+
<link rel="stylesheet" type="text/css" href="https://thara.dev/css/custom.css">
11+
<meta name="og:title" content="Go
12+
ジェネリクスでインターフェース制約を満たしつつ zero
13+
valueで初期化する - thara.dev" />
14+
<meta name="og:type" content="article" />
15+
<meta name="og:site_name" content="thara.dev" />
16+
<meta name="twitter:card" content="summary" />
17+
<meta name="twitter:site" content="@zetta1985" />
18+
<meta name="twitter:title" content="Go
19+
ジェネリクスでインターフェース制約を満たしつつ zero
20+
valueで初期化する - thara.dev" />
21+
<meta name="Hatena::Bookmark" content="nocomment" />
22+
<link rel="author" href="http://www.hatena.ne.jp/zetta1985/" /></head>
23+
<body>
24+
<header>
25+
<h1>Go ジェネリクスでインターフェース制約を満たしつつ zero
26+
valueで初期化する</h1>
27+
<ul>
28+
<li>published: <time>2025-07-21</time></li>
29+
</ul>
30+
</header>
31+
<div role="main">
32+
<article>
33+
<p>例えば、proto.Message
34+
を型制約に持つgenericな関数を書こうとすると、次のような落とし穴がある:</p>
35+
<div class="sourceCode" id="cb1"><pre class="sourceCode go"><code class="sourceCode go"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">func</span> DecodeProtoMessage<span class="op">[</span>T proto<span class="op">.</span>Message<span class="op">](</span>data <span class="op">[]</span><span class="dt">byte</span><span class="op">)</span> <span class="op">(*</span>T<span class="op">,</span> <span class="dt">error</span><span class="op">)</span> <span class="op">{</span></span>
36+
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span></span>
37+
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">var</span> m T</span>
38+
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> err <span class="op">:=</span> proto<span class="op">.</span>Unmarshal<span class="op">(</span>b<span class="op">,</span> m<span class="op">);</span> err <span class="op">!=</span> <span class="ot">nil</span> <span class="op">{</span></span>
39+
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span></span>
40+
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
41+
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="op">&amp;</span>m<span class="op">,</span> <span class="ot">nil</span></span>
42+
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
43+
<p>このとき、<code>T</code><code>*Pet</code>
44+
のようなポインタ型を指定すると、<code>var m T</code>
45+
nilのままとなり、<code>proto.Unmarshal</code> はpanicしてしまう。 Go
46+
のジェネリクスでは、ポインタ型に対するゼロ値の <code>new(T)</code>
47+
<code>var m T</code> は nilなので、non-nil前提の処理ではこうなる。</p>
48+
<h2 id="解決策値型-t-とポインタ型-t-を分離して制約する">解決策:値型 T
49+
とポインタ型 *T を分離して制約する</h2>
50+
<p>そこで、以下のように値型 <code>T</code> に対するポインタ型
51+
<code>*T</code> に制約をかけることで、安全な初期化ができる:</p>
52+
<div class="sourceCode" id="cb2"><pre class="sourceCode go"><code class="sourceCode go"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> ProtoMessagePtr<span class="op">[</span>T any<span class="op">]</span> <span class="kw">interface</span> <span class="op">{</span></span>
53+
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> <span class="op">*</span>T</span>
54+
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a> proto<span class="op">.</span>Message</span>
55+
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
56+
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a></span>
57+
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="kw">func</span> DecodeProtoMessage3<span class="op">[</span>T any<span class="op">,</span> P ProtoMessagePtr<span class="op">[</span>T<span class="op">]](</span>data <span class="op">[]</span><span class="dt">byte</span><span class="op">)</span> <span class="op">(*</span>T<span class="op">,</span> <span class="dt">error</span><span class="op">)</span> <span class="op">{</span></span>
58+
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span></span>
59+
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">var</span> m T</span>
60+
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">var</span> p P <span class="op">=</span> <span class="op">&amp;</span>m</span>
61+
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> err <span class="op">:=</span> proto<span class="op">.</span>Unmarshal<span class="op">(</span>b<span class="op">,</span> p<span class="op">);</span> err <span class="op">!=</span> <span class="ot">nil</span> <span class="op">{</span></span>
62+
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span></span>
63+
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
64+
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="op">&amp;</span>m<span class="op">,</span> <span class="ot">nil</span></span>
65+
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
66+
<p><code>T</code><code>any</code> にもかかわらず、 <code>P</code>
67+
の型制約によって<code>*T</code><code>proto.Message</code>
68+
を満たすことを関数定義が要求していることがポイント。
69+
この方法は、<code>proto.Message</code>
70+
に限らず、任意のinterfaceに応用できる。</p>
71+
<p>使用例:</p>
72+
<div class="sourceCode" id="cb3"><pre class="sourceCode go"><code class="sourceCode go"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a> dst<span class="op">,</span> err <span class="op">:=</span> DecodeProtoMessage<span class="op">[</span>petstorev1<span class="op">.</span>Pet<span class="op">](</span>encodedData<span class="op">)</span></span></code></pre></div>
73+
<p>このように <code>P</code>
74+
は第1型パラメータによって自明なので、関数を利用する際には
75+
<code>ProtoMessagePtr</code> を指定する必要もない。
76+
reflectionやfactoryなどを使わずに、型安全にzero
77+
valueを生成できていい感じ。</p>
78+
</article>
79+
</div>
80+
<footer>
81+
<small>
82+
<a href="https://thara.dev">thara.jp</a>
83+
| &copy; 2025 Tomochika Hara
84+
</small></footer>
85+
</body>
86+
</html>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!DOCTYPE html>
2+
<html lang="ja">
3+
<head>
4+
<meta http-equiv="refresh" content="0; url=/posts/interface_constraint_zero_init_in_go.html">
5+
<link rel="canonical" href="/posts/interface_constraint_zero_init_in_go.html" />
6+
</head>
7+
</body>
8+
</html>

0 commit comments

Comments
 (0)