<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:webfeeds="http://webfeeds.org/rss/1.0">
  <title>Programming カテゴリ | フューチャー技術ブログ</title>
  <subtitle>Programming カテゴリの記事一覧</subtitle>
  <icon>https://future-architect.github.io/feed_icon.png</icon>
  <logo>https://future-architect.github.io/apple-touch-icon.png</logo>
  <webfeeds:icon>https://future-architect.github.io/apple-touch-icon.png</webfeeds:icon>
  <webfeeds:accentColor>258fb8</webfeeds:accentColor>
  <link href="https://future-architect.github.io/categories/Programming/atom.xml" rel="self"/>
  <link href="https://future-architect.github.io/categories/Programming/"/>
  <updated>2026-08-06T15:00:00.000Z</updated>
  <id>https://future-architect.github.io/categories/Programming/</id>
  <generator uri="https://hexo.io/">Hexo</generator>
  <entry>
    <title>Go 1.27の go fix アップデート</title>
    <link href="https://future-architect.github.io/articles/20260807a/"/>
    <id>https://future-architect.github.io/articles/20260807a/</id>
    <published>2026-08-06T15:00:00.000Z</published>
    <updated>2026-08-06T15:00:00.000Z</updated>
    <author><name>真野隼記</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260807a/top.jpg" alt="" width="512" height="286">

<p>The Go gopher was designed by Renee French.</p>
<p>Go 1.27 リリース連載の9本目です。</p>
<h2 id="はじめに">はじめに</h2><p>TIG真野です。</p>
<p>1.26時の前作に続いて、Go 1.27における <code>go fix</code> の機能追加や変更点、背景を解説します。</p>
<p>検証は <code>go1.27rc2 linux/amd64</code> で行いました。</p>
<h2 id="サマリ">サマリ</h2><p>リリースノートには3点書かれています。整理すると次の通りです。</p>
<ol>
<li><strong>モダナイザーの新規追加</strong>: リリースノート上は <code>atomictypes</code>、<code>embedlit</code>、<code>slicesbackward</code>、<code>unsafefuncs</code>の4つが追加されました。ただ、 <code>go tool fix help</code> を実行すると <code>errorsastype</code> も含まれ、実際は5つ追加されているようです。従来分と合わせると登録analyzer数は1.26の22個 → 26個<sup id="fnref:1">1</sup>になりました</li>
<li><strong>fmtappendfの削除</strong>: 既存のモダナイザーでしたがスタイル上の懸念が理由</li>
<li><strong>waitgroupがリネームされた</strong>: <code>go vet</code> 側に先住していた同名のバグ検知analyzerと紛らわしいため、<code>waitgroupgo</code> にリネーム</li>
</ol>
<p>1.26ではイマドキの書き方への置換中心だったのに対し、1.27の新顔は並行処理・unsafeといった「間違えると事故る」内容も含まれています。ツールとしてのレベルアップを感じます。</p>
<h2 id="モダナイザーの新規追加">モダナイザーの新規追加</h2><p>新しいモダナイザー5つについて、<code>go fix -diff ./...</code> の書き換え結果と、背景を見ていきます。</p>
<p>書き換え結果は視認性優先で、<code>package main</code> やimport文の増減（<code>slices</code> の追加など、<code>go fix</code> が自動で面倒を見てくれます）を省略しています。あしからず。</p>
<h3 id="atomictypes-sync-atomic関数を型付きatomicに">atomictypes: sync&#x2F;atomic関数を型付きatomicに</h3><p>Issueは #77352です。</p>
<figure class="highlight diff_go"><figcaption><span>before/after</span></figcaption><table><tbody><tr><td class="code"><pre><span class="line deletion">-<span class="keyword-plain">var</span> counter <span>int64</span></span><br><span class="line addition">+<span class="keyword-plain">var</span> counter atomic.Int64</span><br><span class="line"></span><br><span class="line"> <span><span class="keyword-plain">func</span> <span>increment</span><span>()</span></span> <span>int64</span> {</span><br><span class="line deletion">-	atomic.AddInt64(&amp;counter, <span>1</span>)</span><br><span class="line deletion">-	<span class="keyword-plain">return</span> atomic.LoadInt64(&amp;counter)</span><br><span class="line addition">+	counter.Add(<span>1</span>)</span><br><span class="line addition">+	<span class="keyword-plain">return</span> counter.Load()</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure>

<p><code>atomic.AddInt64(&amp;x, 1)</code> のような関数スタイルを、Go 1.19で追加された型付きatomic（<code>atomic.Int64</code> など。提案は #50860）へ書き換えます。</p>
<p>従来の関数スタイルには2つの困りごとがありました。</p>
<ol>
<li><strong>非atomicアクセスを止められない</strong>: 変数自体はただの <code>int64</code> なので、うっかり <code>x++</code> や <code>x == 0</code> と書けてしまい、データ競合の温床でした。atomicに触るべき変数かどうかは、宣言を見ても分かりません</li>
<li><strong>32bit環境でのアライメント問題</strong>: 64bit系のatomic関数は対象変数が64bitアラインされていることを要求し、structフィールドの並び順次第で実行時パニックする罠が有名だったようです。<code>atomic.Int64</code> 型は自動で正しくアラインされ、この問題ごと消えます</li>
</ol>
<p>atomictypesは他のモダナイザーと毛色が違い、式の書き換えではなく変数宣言そのものを <code>atomic.Int64</code> に変えて、全利用箇所を追随させるという大掛きな変換をします。</p>
<p>その分だけ発動条件は厳格で、手元の検証では、対象変数に非atomicなアクセスが1箇所でもあると（例：<code>&amp;counter</code> を <code>unsafe.Pointer</code> に渡していた）書き換え全体が抑止されました。「全使用箇所がatomic操作である場合のみ書き換える。さもないとデータ競合を隠してしまう」とソースコメントにも明記されています。</p>
<h3 id="embedlit-埋め込みフィールドのネストしたリテラルを直接初期化に">embedlit: 埋め込みフィールドのネストしたリテラルを直接初期化に</h3><p>Issueは#77965です。</p>
<figure class="highlight diff_go"><figcaption><span>before/after</span></figcaption><table><tbody><tr><td class="code"><pre><span class="line"> <span class="comment">// type User struct { Base; Email string }</span></span><br><span class="line"> <span class="comment">// type Base struct { ID int; Name string }</span></span><br><span class="line"> <span class="keyword-plain">return</span> User{</span><br><span class="line deletion">-	Base:  Base{ID: <span>1</span>, Name: <span>&quot;mano&quot;</span>},</span><br><span class="line addition">+	ID: <span>1</span>, Name: <span>&quot;mano&quot;</span>,</span><br><span class="line"> 	Email: <span>&quot;mano@example.com&quot;</span>,</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure>

<p>この文法の拡張についてはGo 1.27の構造体リテラルのキー拡張で、埋め込みフィールドの初期化が楽になった を確認ください。</p>
<p>embedlitはこの新記法への書き換えで、実は2パターンあります。</p>
<figure class="highlight go"><figcaption><span>書き換えパターン</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment">// パターン1: ネスト除去</span></span><br><span class="line">t := T&#123;U: U&#123;x: <span class="number">1</span>&#125;&#125;  →  t := T&#123;x: <span class="number">1</span>&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// パターン2: リテラル直後の代入の取り込み</span></span><br><span class="line">t := T&#123;&#125;</span><br><span class="line">t.x = <span class="number">1</span></span><br><span class="line"><span class="comment">// ↓</span></span><br><span class="line">t := T&#123;x: <span class="number">1</span>&#125;</span><br></pre></td></tr></table></figure>

<p>新しい言語機能と同時にモダナイザーを同梱してリリースする、素敵な対応です。</p>
<h3 id="slicesbackward-手書きの逆順ループを-slices-Backward-に">slicesbackward: 手書きの逆順ループを slices.Backward に</h3><p>Issueは#78484です。</p>
<figure class="highlight diff_go"><figcaption><span>before/after</span></figcaption><table><tbody><tr><td class="code"><pre><span class="line"> <span><span class="keyword-plain">func</span> <span>printReverse</span><span>(items []<span>string</span>)</span></span> {</span><br><span class="line deletion">-	<span class="keyword-plain">for</span> i := <span>len</span>(items) - <span>1</span>; i &gt;= <span>0</span>; i-- {</span><br><span class="line deletion">-		fmt.Println(items[i])</span><br><span class="line addition">+	<span class="keyword-plain">for</span> _, item := <span class="keyword-plain">range</span> slices.Backward(items) {</span><br><span class="line addition">+		fmt.Println(item)</span><br><span class="line"> 	}</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure>

<p><code>slices.Backward()</code> は初見でした。便利そうです。</p>
<p>この手書きの逆順ループ、<code>len(s) - 1</code>、<code>&gt;= 0</code>、<code>i--</code> の3点セットを毎回正しく書く必要があり、<code>&gt; 0</code> と書いて先頭要素を取りこぼす、などのミスが定番でした。Go 1.23のイテレータ導入（#61899）で <code>slices.Backward</code> が入り、宣言的に書けるようになっていたところへの追随です。</p>
<p>前方ループについては 同じ go fix の rangeint（<code>for i := 0; i &lt; n; i++</code> → <code>for i := range n</code>）が既にありましたが、逆順ループを検出・書き換えする既存linterは調べた範囲で見当たらず、おそらく初の自動化です。</p>
<p>なお <code>s[i]</code> への代入があるループは書き換えないことを手元で確認しました。読み取り専用ループのみが対象で、インデックスを他の用途にも使っている場合は <code>for i, v := range slices.Backward(s)</code> と両変数を残してくれます。</p>
<h3 id="unsafefuncs-uintptrのポインタ演算を-unsafe-Add-に">unsafefuncs: uintptrのポインタ演算を unsafe.Add に</h3><p>Issueは #76648 です。</p>
<div class="code-block"><figure class="highlight diff_go"><input type="checkbox" id="code-wrap-1m1uxao-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1m1uxao-1" title="コードの折り返しを切り替える"></label><figcaption><span>before/after</span></figcaption><table><tbody><tr><td class="code"><pre><span class="line"> <span><span class="keyword-plain">func</span> <span>secondElem</span><span>(p unsafe.Pointer, size <span>uintptr</span>)</span></span> unsafe.Pointer {</span><br><span class="line deletion">-	<span class="keyword-plain">return</span> unsafe.Pointer(<span>uintptr</span>(p) + size)</span><br><span class="line addition">+	<span class="keyword-plain">return</span> unsafe.Add(p, size)</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure></div>

<p>ポインタ演算のためだけに <code>uintptr</code> へ変換して足し算して戻す旧イディオムは、冗長なだけでなく危険でした。</p>
<p><code>uintptr</code> はただの整数なのでGCがポインタとして追跡しません。変換と逆変換を1つの式で完結させれば安全ですが、うっかり <code>uintptr</code> 値を変数に置いた瞬間、GC移動後のdanglingポインタになり得ます。Go 1.17で追加された <code>unsafe.Add()</code> を使うことでこのリスクを軽減できます。</p>
<p>名前が複数形（unsafe<strong>funcs</strong>）なのに、現状書き換えるのは <code>unsafe.Add</code> だけという点は補足しておきます。ソースには <code>unsafe.String</code> &#x2F; <code>unsafe.Slice</code> 系対応のTODOコメントがあり、将来の拡張を見込んでいると思われます。</p>
<h3 id="errorsastype-errors-AsType-T-へ書き換えてくれる">errorsastype: errors.AsType[T] へ書き換えてくれる</h3><p>rc2の <code>go tool fix help</code> を眺めていて気づいたのですが、リリースノートに記載のない <code>errorsastype</code> も登録されています。API自体の提案は #51945、モダナイザーのIssueは #75692です。</p>
<div class="code-block"><figure class="highlight diff_go"><input type="checkbox" id="code-wrap-1m1uxao-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1m1uxao-2" title="コードの折り返しを切り替える"></label><figcaption><span>before/after</span></figcaption><table><tbody><tr><td class="code"><pre><span class="line"> <span><span class="keyword-plain">func</span> <span>handle</span><span>(err <span>error</span>)</span></span> {</span><br><span class="line deletion">-	<span class="keyword-plain">var</span> nf *NotFoundError</span><br><span class="line deletion">-	<span class="keyword-plain">if</span> errors.As(err, &amp;nf) {</span><br><span class="line addition">+	<span class="keyword-plain">if</span> nf, ok := errors.AsType[*NotFoundError](err); ok {</span><br><span class="line"> 		fmt.Println(<span>&quot;key:&quot;</span>, nf.Key)</span><br><span class="line"> 	}</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure></div>

<p>Go 1.26で追加されたジェネリクス版 <code>errors.AsType[T]</code> への書き換えです。<code>errors.As</code> は第2引数に「エラー型へのポインタ」以外を渡すと実行時パニックという罠があり、ジェネリクス版はこれをコンパイル時に排除できます。変数のスコープもif文に閉じて、1行減ります。</p>
<p>ちなみに、<code>errors.As</code> に渡した変数をif文の外でも使っているコードは書き換えないことを確認したので、変な書き換えはしなさそうな安心感があります。</p>
<p>リリースノートは正式版までに更新されるかもしれません。</p>
<h2 id="fmtappendf-が排除された">fmtappendf が排除された</h2><p>Issueは#77581です。</p>
<p>fmtappendf は <code>[]byte(fmt.Sprintf(...))</code> を <code>fmt.Appendf(nil, ...)</code> に書き換えるモダナイザーでした。go1.26.5 の <code>go fix</code> では次のように書き換えられます。</p>
<figure class="highlight diff_go"><figcaption><span>Go1.26時の書き換え例</span></figcaption><table><tbody><tr><td class="code"><pre><span class="line"> <span><span class="keyword-plain">func</span> <span>formatBytes</span><span>(n <span>int</span>)</span></span> []<span>byte</span> {</span><br><span class="line deletion">-	<span class="keyword-plain">return</span> []<span>byte</span>(fmt.Sprintf(<span>&quot;count=%d&quot;</span>, n))</span><br><span class="line addition">+	<span class="keyword-plain">return</span> fmt.Appendf(<span>nil</span>, <span>&quot;count=%d&quot;</span>, n)</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure>

<p>go1.27rc2 では削除済みのため <code>go fix</code> では何も起きません。</p>
<p>これを外すに至った理由は2つです。</p>
<ol>
<li><strong>より良い書き換えを邪魔する</strong>: 「このモダナイザーが <code>fmt.Appendf</code> を挿入する箇所の半分以上は、もう少し広い文脈を見ればずっと良い変更がある」。代表例が <code>w.Write([]byte(fmt.Sprintf(...)))</code> で、機械的に <code>w.Write(fmt.Appendf(nil, ...))</code> にされますが、本来あるべき姿は <code>fmt.Fprintf(w, ...)</code> です</li>
<li><strong>残るケースでも改善が微妙</strong>: 「コードがわずかに分かりにくくなる（なぜ何かを”append”しているのか？）」「<code>nil</code> を書かされるのはばかばかしい」</li>
</ol>
<p>性能面でも <code>fmt.Appendf(nil, ...)</code> の方が<strong>むしろ遅い</strong>というベンチマーク報告（#73666）が別途あり、「アロケーション削減」という当初の建前も揺らいでいた、という背景もあります。</p>
<p>fmtappendf はGo 1.26の <code>go fix</code> 入りからわずか1リリースでの退場となりました。一度入れた機能でも理由があれば引っ込める、という判断が健全だと感じます。偉い！</p>
<h2 id="waitgroup-改め-waitgroupgo-は、何と被っていたのか">waitgroup 改め waitgroupgo は、何と被っていたのか</h2><p>Issueは#77560です。</p>
<p>内容ですが一言でいうと、<code>go vet</code> 側に同名の <code>waitgroup</code> analyzer が先住していました。どちらもGo 1.25で追加されたものです。</p>
<ul>
<li><strong>vet版waitgroup</strong>: goroutineの中で <code>wg.Add(1)</code> を呼んでしまい、<code>Wait</code> とレースして早期リターンするバグを検知するチェッカー</li>
<li><strong>fix版waitgroup</strong>: <code>wg.Add(1)</code> &#x2F; <code>go func()</code> &#x2F; <code>defer wg.Done()</code> の3点セットを <code>wg.Go()</code>に畳むモダナイザー</li>
</ul>
<p>同じ <code>sync.WaitGroup</code> 対象、同じ名前で、片方はバグ検知・片方はモダン化という別物が同居していました。go1.26.5で <code>go tool vet help waitgroup</code> と <code>go tool fix help waitgroup</code> を叩くと、実際に全く違う説明が返ってきて確かに紛らわしいです。早めに修正できて良かったと思います。</p>
<h2 id="さいごに">さいごに</h2><p>Go 1.27の <code>go fix</code> アップデートを紹介しました。1.27でも安全な書き換えしか行ないポリシーで開発されているため、安心して使えそうです。素敵！</p>
<p>こうしたモダン化は、今ではAIエージェントがコードを書くついでにやってくれる場面も多いですが、ツール側で <strong>決定的に</strong> 行ってくれることの価値も依然として重要です。世の中のGoコードがモダンな書き方へ収束していけば、それを学習するモデル側の精度も上がり、AIがより良いコードを書いてくれる、という好循環が回ります。比較的、地味なコマンドですが、長期的なGo言語の発展にも寄与してくれると思います。</p>
<p>最後まで読んでいただき、ありがとうございました！</p>
<div id="footnotes"><hr><div id="footnotelist"><ol style="list-style:none; padding-left: 0;"><li id="fn:1"><span style="vertical-align: top; padding-right: 10px;">1.</span><span style="vertical-align: top;">前回記事ではGo 1.26時点のモダン化機能を24個と紹介しましたが、<code>go tool fix help</code> で実測すると go1.26.0 / go1.26.5 ともに登録analyzerは22個でした（マイナーアップデートで減った訳ではなさそうです）。24という数字は、<code>go fix</code> には採用されていないgopls専用のモダナイザー（ベンチマーク結果を歪め得る <code>bloop</code> や、nil保存でない <code>appendclipped</code> / <code>slicesdelete</code> など。後述）を含めて数えていたためと思われます。本記事の個数はすべて <code>go fix</code> 側の実測値です。</span> ↩</li></ol></div></div>]]></content>
    <summary type="html">1.26時の前作に続いて、Go 1.27における go fix の機能追加や変更点、背景を解説します。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.27" scheme="https://future-architect.github.io/tags/Go1-27/"/>
  </entry>
  <entry>
    <title>Go 1.27リリース連載：ポスト量子暗号</title>
    <link href="https://future-architect.github.io/articles/20260806a/"/>
    <id>https://future-architect.github.io/articles/20260806a/</id>
    <published>2026-08-05T15:00:00.000Z</published>
    <updated>2026-08-05T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260806a/top.jpg" alt="" width="720" height="402">

<p>The Go gopher was designed by Renee French.</p>
<p>Go 1.27リリース連載の8本目です。</p>
<h2 id="はじめに">はじめに</h2><p>Go 1.27で<code>crypto/mldsa</code>が追加されました。ポスト量子暗号のデジタル署名ML-DSAの実装です。</p>
<p>Goのポスト量子暗号への対応はGo 1.24から始まっています。鍵交換のML-KEMが入り、TLSの既定で有効になりました。Go 1.27で署名が加わり、鍵交換と署名の両方が標準ライブラリでそろいました。</p>
<p>今回は、量子コンピュータが暗号のどこに影響するのかという基礎から入り、ML-KEMの動作確認、ML-DSAでの署名と検証、ML-DSA証明書でのTLSサーバー構築までを、手元で動かしながら紹介します。</p>
<h2 id="量子コンピュータと暗号">量子コンピュータと暗号</h2><p>まず、いま使われている公開鍵暗号がなぜ安全なのかを整理します。代表例として、HTTPSの証明書やSSHの鍵で長く使われてきたRSAを取り上げます。</p>
<p>RSAは、大きな素数を2つ掛け合わせた数を公開鍵として使い、元の素数2つは秘密にしておきます。掛け算は簡単ですが、その積から元の素数に戻す作業、つまり素因数分解は、桁数が増えるほど難しくなります。この積が何ビットの数かを鍵長と呼びます。RSA-2048なら積が2048ビットの数で、10進に直すと600桁あまりになります。</p>
<p>楕円曲線暗号（ECDSAやECDH）とDiffie-Hellman（DH）は離散対数と呼ばれる別の問題を使いますが、基本的な考え方は同じです。片方向の計算は簡単で、逆向きは現実的な時間では解けない、という非対称性に安全性を置いています。</p>
<p>しかし、この「現実的な時間では解けない」は、古典コンピュータ（いま私たちが使っているコンピュータのことで、量子コンピュータと区別するときにこう呼びます）を相手にした話です。</p>
<h3 id="量子コンピュータとは">量子コンピュータとは</h3><p>量子コンピュータは、量子ビットという単位で計算する計算機です。古典コンピュータのビットが0か1のどちらかを取るのに対して、量子ビットは0と1が重なり合った状態を取れます。重ね合わせのまま計算を進め、最後に読み出した時点で0か1に確定します。</p>
<p>この重ね合わせを活かした専用のアルゴリズムを組むと、特定の問題を桁違いに速く解けます。裏を返すと、専用アルゴリズムが見つかっていない計算処理は速くならないと言われています。また、大きな問題を解くには多くの量子ビットが必要で、量子コンピュータの規模は量子ビットの数で決まります。</p>
<p>そして素因数分解と離散対数には、その専用アルゴリズムが見つかっています。</p>
<h3 id="Shorのアルゴリズム">Shorのアルゴリズム</h3><p>1994年にPeter Shorが発表した量子コンピュータ向けのアルゴリズムは、素因数分解と離散対数を、古典コンピュータよりはるかに少ない手間で解きます。</p>
<p>差が出るのは鍵長を伸ばしたときの増え方です。</p>
<div class="code-block"><figure class="highlight text"><input type="checkbox" id="code-wrap-ukyanq-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ukyanq-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">解読にかかる手間（縦軸は対数目盛）</span><br><span class="line">  ^</span><br><span class="line">  |                              *   古典コンピュータ</span><br><span class="line">  |                         *</span><br><span class="line">  |                    *</span><br><span class="line">  |               *</span><br><span class="line">  |          *</span><br><span class="line">  |     *</span><br><span class="line">  |  *</span><br><span class="line">  |</span><br><span class="line">  |  o    o    o    o    o    o      量子コンピュータ（Shorのアルゴリズム）</span><br><span class="line">  +---------------------------------&gt;</span><br><span class="line">    512  1024  2048  4096          RSAの鍵長（ビット）</span><br></pre></td></tr></table></figure></div>

<p>RSAの鍵長を1024ビットから2048ビットへ倍にすると、古典コンピュータでの解読の手間はおよそ43億倍に増えます。NISTの見積もりで、安全性が80ビット相当から112ビット相当へ上がるためです。この増え方があったので、計算機が速くなっても鍵長を伸ばして対応できました。</p>
<p>同じ変更に対して、Shorのアルゴリズムの手間は8倍程度にしか増えません。実装によりますが、手間は鍵長の3乗あたりで増えるとされていて、鍵長が倍なら2の3乗で8倍です。8倍で済むなら、攻撃側は量子ビットの数を増やすだけで対処できてしまいます。</p>
<p>つまり、鍵長を伸ばして攻撃側の計算時間を引き延ばす、という従来の防御が使えなくなります。Shorのアルゴリズムを実行できる数の量子ビットがそろえば、RSAとECDSA、ECDH、DHはまとめて安全性を失います。鍵長の調整では防ぎきれないため、アルゴリズムそのものを入れ替える必要があります。</p>
<h3 id="対称鍵暗号とハッシュ関数">対称鍵暗号とハッシュ関数</h3><p>一方、AESやSHA-2、SHA-3への影響はずっと小さいとされています。これらには素因数分解のような数学的な構造がなく、Shorのアルゴリズムが働く余地がないからです。解読の手段は基本的に総当たりです。</p>
<p>その総当たりを速くするのがGroverのアルゴリズムで、探索の手間を平方根まで縮めます。128ビットの鍵なら、2の128乗回の試行が2の64乗回相当になります。ただし、こちらは鍵長を伸ばせば相殺できます。AES-256であれば2の128乗回相当が残るので、いま128ビット鍵に期待している水準を保てます。</p>
<p>ここまでを整理すると、次のとおりです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>対象</th>
<th>解読の手がかり</th>
<th>量子コンピュータの影響</th>
<th>対応</th>
</tr>
</thead>
<tbody><tr>
<td>RSA、ECDSA、ECDH、DH</td>
<td>素因数分解、離散対数</td>
<td>Shorのアルゴリズムで解ける</td>
<td>アルゴリズムを入れ替える</td>
</tr>
<tr>
<td>AES、SHA-2、SHA-3</td>
<td>総当たりのみ</td>
<td>Groverのアルゴリズムで探索が平方根に短縮</td>
<td>鍵長やダイジェスト長を伸ばす</td>
</tr>
</tbody></table></div>
<h3 id="NISTが標準化したアルゴリズム">NISTが標準化したアルゴリズム</h3><p>暗号を解ける規模の量子コンピュータが現れても、AESやSHA-2、SHA-3は鍵長やダイジェスト長を伸ばせば使い続けられます。手を打たなければならないのは、表の上の行にあるRSAとECDSA、ECDH、DHです。鍵長では防ぎきれないため、代わりのアルゴリズムが必要になります。</p>
<p>その置き換え先として、NISTは公募と評価を経て、2024年8月に3つの標準を確定しました。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>標準</th>
<th>アルゴリズム</th>
<th>用途</th>
</tr>
</thead>
<tbody><tr>
<td>FIPS 203</td>
<td>ML-KEM（旧Kyber）</td>
<td>鍵交換</td>
</tr>
<tr>
<td>FIPS 204</td>
<td>ML-DSA（旧Dilithium）</td>
<td>デジタル署名</td>
</tr>
<tr>
<td>FIPS 205</td>
<td>SLH-DSA（旧SPHINCS+）</td>
<td>デジタル署名</td>
</tr>
</tbody></table></div>
<p>この3つのように、量子コンピュータを使っても効率よく解く方法が見つかっていない問題に安全性を置く暗号を、ポスト量子暗号（Post-Quantum Cryptography）と呼びます。量子コンピュータで動かす暗号ではなく、いまのコンピュータの上でそのまま動きます。</p>
<p>ML-KEMとML-DSAの先頭にあるMLはModule-Lattice、つまり格子を指します。格子は空間に規則的に並んだ点の集まりです。</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">2次元の格子</span><br><span class="line"></span><br><span class="line">   *     *     *     *     *</span><br><span class="line"></span><br><span class="line">   *     *     *     *     *</span><br><span class="line"></span><br><span class="line">   *     *     X  x  *     *</span><br><span class="line"></span><br><span class="line">   *     *     *     *     *</span><br><span class="line"></span><br><span class="line">   *  格子点</span><br><span class="line">   x  目標の座標（格子点の上にはない）</span><br><span class="line">   X  x に最も近い格子点</span><br></pre></td></tr></table></figure>

<p>ここで「指定された座標に最も近い格子点を探す」という問題を考えます。2次元なら目で見て探せます。</p>
<p>ML-KEMとML-DSAが使うのは、数百次元の格子です。ML-KEM-768という名前の768が、その次元を表しています。次元がここまで上がると、最も近い格子点を探すのに、知られているどのアルゴリズムでも次元に対して指数関数的な時間がかかります。</p>
<p>Shorのアルゴリズムは素因数分解と離散対数に特化した手法なので、格子問題には効きません。格子問題を効率よく解く量子アルゴリズムも、今のところ見つかっていません。この見込みのうえに、新しい標準が組まれています。</p>
<p>Goが標準ライブラリに入れたのは、このうちML-KEM（Go 1.24）とML-DSA（Go 1.27）です。</p>
<h2 id="Harvest-Now-Decrypt-Later">Harvest Now, Decrypt Later</h2><p>Shorのアルゴリズムを実行してRSA-2048を破るには、100万個規模の量子ビットが必要という見積もりがあります（Gidney, 2025）。いま動いている量子コンピュータの量子ビットは1000個規模なので、まだ3桁の開きがあります。この開きがいつ埋まるかの確かな見通しはありません。それでも今から移行する理由が、Harvest Now, Decrypt Later（HNDL）と呼ばれる攻撃です。攻撃者は暗号化された通信を今のうちに保存しておき、解読できる量子コンピュータが手に入った時点でまとめて復号することを狙っています。</p>
<pre class="mermaid" data-mermaid="218037aa7cdee01345569a945d1869ed6f3c1646936d4bdfb6856d52ea76e214">flowchart TB
    comm["今の暗号化通信"] -->|"傍受して保存する"| stored["攻撃者の手元に残り続ける暗号文"]
    stored -.->|"今すぐ試す"| fail["古典コンピュータでは解読できない"]
    stored ==>|"数年後に試す"| ok["量子コンピュータで解読できる"]
    ok ==> plain["当時の平文が読まれる"]</pre>

<p>暗号文は攻撃者の手元に残り続けるので、解読できるかどうかは、保存された時点ではなく攻撃者が試した時点の技術で決まります。今は解読できないという状態が、そのまま安全を意味しません。</p>
<p>そして、この攻撃は後から対策できません。移行を終えた後でも、それ以前に流れたトラフィックは保存された側の手元に残っています。移行が早いほど、守れる通信が増えます。期限の目安として、NISTはドラフト段階のNIST IR 8547で、量子コンピュータで破れる公開鍵暗号を2035年より後は使用できないものとする方針を示しています。RSA-2048相当の強度のものは、2030年より後は非推奨です。</p>
<p>一方、デジタル署名にこの図式は当てはまりません。暗号文は後から解読できれば中身の秘密が手に入りますが、署名の役割は受け取ったものが本物かをその場で確かめることで、その確認は受け取った時点で終わっています。署名を保存しておいて後から破っても、攻撃者が得るものはありません。署名で問題になるのは、解読できる量子コンピュータが現れた後に、攻撃者が新しい署名を偽造できるようになる点です。</p>
<p>つまり、鍵交換と署名では急ぎ方が違います。鍵交換は、通信がいまも保存され続けているため、今すぐ移行を始める必要があります。署名は、偽造が現実になる前に移行が終わっていれば間に合います。Goもこの順番で、鍵交換をGo 1.24（2025年2月）で、署名をGo 1.27（2026年8月）で入れました。</p>
<h2 id="Go-1-24のML-KEM">Go 1.24のML-KEM</h2><p>ML-KEMはKEM（Key Encapsulation Mechanism）、つまり鍵交換の仕組みです。Go 1.24で<code>crypto/mlkem</code>が入り、同時にTLSの鍵交換にも組み込まれました。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>Go</th>
<th>既定に追加された鍵交換</th>
</tr>
</thead>
<tbody><tr>
<td>1.24</td>
<td><code>X25519MLKEM768</code></td>
</tr>
<tr>
<td>1.26</td>
<td><code>SecP256r1MLKEM768</code>、<code>SecP384r1MLKEM1024</code></td>
</tr>
</tbody></table></div>
<p>いずれも従来の楕円曲線とML-KEMを組み合わせたハイブリッドです。ML-KEMに未知の弱点が見つかっても、楕円曲線側の強度が残ります。<code>Config.CurvePreferences</code>が<code>nil</code>のときに有効になるため、Go 1.24以降でビルドしたクライアントとサーバーは、相手も対応していれば、コードを変えなくてもポスト量子の鍵交換を使います。</p>
<p>実際に使われたかどうかは、<code>ConnectionState.CurveID</code>（Go 1.25で追加されたフィールドです）で確認できます。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-ukyanq-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ukyanq-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;crypto/tls&quot;</span></span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;log&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	conn, err := tls.Dial(<span class="string">&quot;tcp&quot;</span>, <span class="string">&quot;go.dev:443&quot;</span>, &amp;tls.Config&#123;MinVersion: tls.VersionTLS12&#125;)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		log.Fatal(err)</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">defer</span> conn.Close()</span><br><span class="line"></span><br><span class="line">	st := conn.ConnectionState()</span><br><span class="line">	fmt.Printf(<span class="string">&quot;version: %s\n&quot;</span>, tls.VersionName(st.Version))</span><br><span class="line">	fmt.Printf(<span class="string">&quot;curve:   %v\n&quot;</span>, st.CurveID)</span><br><span class="line">	fmt.Printf(<span class="string">&quot;cipher:  %s\n&quot;</span>, tls.CipherSuiteName(st.CipherSuite))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<div class="code-block"><figure class="highlight text"><input type="checkbox" id="code-wrap-ukyanq-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ukyanq-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">$ go1.27rc2 run .</span><br><span class="line">version: TLS 1.3</span><br><span class="line">curve:   X25519MLKEM768          ← 鍵交換はX25519とML-KEM-768のハイブリッド</span><br><span class="line">cipher:  TLS_AES_128_GCM_SHA256</span><br></pre></td></tr></table></figure></div>

<p><code>GODEBUG=tlsmlkem=0</code>を付けるとポスト量子の鍵交換が既定から外れるので、比較すると違いが分かります。</p>
<div class="code-block"><figure class="highlight text"><input type="checkbox" id="code-wrap-ukyanq-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ukyanq-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">$ GODEBUG=tlsmlkem=0 go1.27rc2 run .</span><br><span class="line">version: TLS 1.3</span><br><span class="line">curve:   X25519                  ← 鍵交換は従来の楕円曲線だけ</span><br><span class="line">cipher:  TLS_AES_128_GCM_SHA256</span><br></pre></td></tr></table></figure></div>

<p>2つの出力で変わったのは<code>curve</code>だけで、<code>cipher</code>は同じ<code>TLS_AES_128_GCM_SHA256</code>のままです。ポスト量子への移行で入れ替わるのは鍵交換だけで、対称鍵暗号のAESはそのまま使われ続けます。</p>
<h2 id="Go-1-27のML-DSA">Go 1.27のML-DSA</h2><p>配布されたソフトウェアが改変されていないかを確かめる作業は、ハッシュの比較と署名の検証を組み合わせて成り立っています。この検証は署名アルゴリズムが破られていないことを前提にしているため、署名側の移行も必要になります。</p>
<p>ソフトウェアへの署名で広く使われているSigstoreは、Goで書かれています。Sigstoreはポスト量子暗号をすぐには導入せず、信頼できる実装がGoの<code>crypto</code>パッケージに入ることを導入の前提条件としていました。Go 1.27の<code>crypto/mldsa</code>で、その前提条件がそろったことになります。Sigstoreの検証の仕組みは別の記事で整理しました。</p>
<p>ML-DSAはFIPS 204で標準化されたデジタル署名です。<code>crypto/mldsa</code>は3つのパラメータセットを提供します。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>パラメータセット</th>
<th>公開鍵</th>
<th>署名</th>
<th>秘密鍵（シード）</th>
</tr>
</thead>
<tbody><tr>
<td>ML-DSA-44</td>
<td>1312バイト</td>
<td>2420バイト</td>
<td>32バイト</td>
</tr>
<tr>
<td>ML-DSA-65</td>
<td>1952バイト</td>
<td>3309バイト</td>
<td>32バイト</td>
</tr>
<tr>
<td>ML-DSA-87</td>
<td>2592バイト</td>
<td>4627バイト</td>
<td>32バイト</td>
</tr>
</tbody></table></div>
<p>比較のために、いま広く使われているEd25519を並べると、公開鍵は32バイト、署名は64バイトです。ML-DSAでは最小のML-DSA-44でも署名が2420バイトあり、Ed25519の約38倍です。一方で秘密鍵は32バイトのシードだけです。<code>crypto/mldsa</code>は展開済みの秘密鍵形式を扱いません。proposalの議論では、展開済みの鍵はシードより大きく読み込みも遅いうえに危険であり、利点がないと判断されています。</p>
<h3 id="署名して検証する">署名して検証する</h3><p>短いメッセージに署名して、検証まで通してみます。ML-DSAはメッセージをそのまま署名対象にできるため、事前のハッシュ計算は不要です。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-ukyanq-5" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ukyanq-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;crypto/mldsa&quot;</span></span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;log&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	message := []<span class="type">byte</span>(<span class="string">&quot;Hello, World&quot;</span>)</span><br><span class="line"></span><br><span class="line">	sk, err := mldsa.GenerateKey(mldsa.MLDSA65())</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		log.Fatal(err)</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	opts := &amp;mldsa.Options&#123;Context: <span class="string">&quot;example&quot;</span>&#125;</span><br><span class="line">	sig, err := sk.Sign(<span class="literal">nil</span>, message, opts)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		log.Fatal(err)</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	fmt.Printf(<span class="string">&quot;params:      %s\n&quot;</span>, sk.PublicKey().Parameters())</span><br><span class="line">	fmt.Printf(<span class="string">&quot;private key: %d bytes\n&quot;</span>, <span class="built_in">len</span>(sk.Bytes()))</span><br><span class="line">	fmt.Printf(<span class="string">&quot;public key:  %d bytes\n&quot;</span>, <span class="built_in">len</span>(sk.PublicKey().Bytes()))</span><br><span class="line">	fmt.Printf(<span class="string">&quot;signature:   %d bytes\n&quot;</span>, <span class="built_in">len</span>(sig))</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 検証側は公開鍵のバイト列から鍵を復元する</span></span><br><span class="line">	pk, err := mldsa.NewPublicKey(mldsa.MLDSA65(), sk.PublicKey().Bytes())</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		log.Fatal(err)</span><br><span class="line">	&#125;</span><br><span class="line">	fmt.Printf(<span class="string">&quot;verify:            %v\n&quot;</span>, mldsa.Verify(pk, message, sig, opts))</span><br><span class="line"></span><br><span class="line">	<span class="comment">// メッセージが1文字でも変わると検証に失敗する</span></span><br><span class="line">	tampered := []<span class="type">byte</span>(<span class="string">&quot;Hello, World!&quot;</span>)</span><br><span class="line">	fmt.Printf(<span class="string">&quot;verify (tampered): %v\n&quot;</span>, mldsa.Verify(pk, tampered, sig, opts))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">$ go1.27rc2 run .</span><br><span class="line">params:      ML-DSA-65</span><br><span class="line">private key: 32 bytes</span><br><span class="line">public key:  1952 bytes</span><br><span class="line">signature:   3309 bytes</span><br><span class="line">verify:            &lt;nil&gt;</span><br><span class="line">verify (tampered): mldsa: invalid signature</span><br></pre></td></tr></table></figure>

<p>秘密鍵32バイト、公開鍵1952バイト、署名3309バイトという出力は、先ほどの表のML-DSA-65の行と一致します。<code>verify</code>の<code>&lt;nil&gt;</code>は、<code>Verify</code>がエラーを返さなかった、つまり検証に成功したという意味です。最後の行は末尾に<code>!</code>を1文字足したメッセージに対する結果で、同じ署名では検証が通らなくなっています。</p>
<p><code>sk.Sign</code>の第1引数は乱数源を渡すための<code>io.Reader</code>ですが、この引数は使われず、乱数は内部の安全な乱数源から取られます。<code>crypto.Signer</code>インターフェースに形を合わせるためだけにあるので、<code>nil</code>を渡します。<code>*mldsa.PrivateKey</code>は<code>crypto.Signer</code>を実装しているので、このインターフェースを受け取る既存の署名処理にそのまま差し込めます。</p>
<p><code>Options.Context</code>は署名の用途を区別する文字列です。署名時と検証時で一致しないと検証が失敗するため、同じ鍵で用途の違う署名を作るときに境界を引けます。使わないなら<code>Options</code>ごと<code>nil</code>を渡せます。</p>
<h2 id="x509とTLSで動かす">x509とTLSで動かす</h2><p>Go 1.27では<code>crypto/x509</code>と<code>crypto/tls</code>もML-DSAに対応しました。<code>x509.CreateCertificate</code>に<code>*mldsa.PublicKey</code>と<code>*mldsa.PrivateKey</code>をそのまま渡せます。あわせて、標準ライブラリのソースツリーにある自己署名証明書の生成ツール<code>crypto/tls/generate_cert.go</code>に<code>--mldsa</code>フラグが追加されました。</p>
<h3 id="証明書を作る">証明書を作る</h3><div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-ukyanq-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ukyanq-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go1.27rc2 run $(go1.27rc2 <span class="built_in">env</span> GOROOT)/src/crypto/tls/generate_cert.go --host=localhost --mldsa</span></span><br><span class="line">2026/08/06 00:26:49 wrote cert.pem</span><br><span class="line">2026/08/06 00:26:49 wrote key.pem</span><br></pre></td></tr></table></figure></div>

<p>ML-DSA-44の鍵で自己署名証明書ができます。<code>key.pem</code>は128バイトです。秘密鍵が32バイトのシードだけなので、この小ささになります。</p>
<h3 id="サーバーを立てる">サーバーを立てる</h3><div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-ukyanq-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ukyanq-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;log&quot;</span></span><br><span class="line">	<span class="string">&quot;net/http&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	http.HandleFunc(<span class="string">&quot;/&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">		fmt.Fprintln(w, <span class="string">&quot;hello over post-quantum TLS&quot;</span>)</span><br><span class="line">	&#125;)</span><br><span class="line">	log.Fatal(http.ListenAndServeTLS(<span class="string">&quot;localhost:8443&quot;</span>, <span class="string">&quot;cert.pem&quot;</span>, <span class="string">&quot;key.pem&quot;</span>, <span class="literal">nil</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>ML-DSA固有の記述はありません。<code>ListenAndServeTLS</code>にPEMファイルのパスを渡すだけで、従来のHTTPSサーバーと同じコードです。</p>
<p>先ほどの<code>cert.pem</code>と<code>key.pem</code>があるディレクトリで実行すると、待ち受けが始まります。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go1.27rc2 run .</span></span><br></pre></td></tr></table></figure>

<h3 id="接続して確認する">接続して確認する</h3><p>別のターミナルから<code>openssl s_client</code>で接続します。ML-DSAを扱うにはOpenSSL 3.5以降が必要です。末尾の<code>&lt; /dev/null</code>は、接続確認だけしてすぐ終了させるための指定です。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-ukyanq-8" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ukyanq-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">openssl s_client -connect localhost:8443 -CAfile cert.pem -servername localhost -brief &lt; /dev/null</span></span><br></pre></td></tr></table></figure></div>

<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">Connecting to 127.0.0.1</span><br><span class="line">CONNECTION ESTABLISHED</span><br><span class="line">Protocol version: TLSv1.3</span><br><span class="line">Ciphersuite: TLS_AES_128_GCM_SHA256</span><br><span class="line">Peer certificate: O=Acme Co</span><br><span class="line">Hash used: UNDEF</span><br><span class="line">Signature type: mldsa44</span><br><span class="line">Verification: OK</span><br><span class="line">Negotiated TLS1.3 group: X25519MLKEM768</span><br><span class="line">DONE</span><br></pre></td></tr></table></figure>

<p><code>Signature type: mldsa44</code>が証明書の署名アルゴリズム、<code>Verification: OK</code>がその署名の検証結果、<code>Negotiated TLS1.3 group: X25519MLKEM768</code>が鍵交換です。署名と鍵交換の両方がポスト量子暗号になっています。</p>
<p>Goで立てたサーバーに対してOpenSSLが検証に成功しているので、実装をまたいで動くことも確認できます。</p>
<h3 id="証明書のサイズ">証明書のサイズ</h3><p>証明書には、所有者の公開鍵と発行者による署名がほぼそのまま入ります。そのため証明書のサイズは、「署名して検証する」で見た数値からおおよそ決まります。ML-DSA-65なら、公開鍵1952バイト + 署名3309バイト + 残り241バイト（所有者名や有効期間などのフィールド）で5502バイトです。</p>
<p>同じ<code>x509.Certificate</code>テンプレートを使い、鍵を作る関数だけを差し替えて、アルゴリズムごとの自己署名証明書を作りました。<code>ed25519.GenerateKey</code>、<code>ecdsa.GenerateKey</code>、<code>rsa.GenerateKey</code>、<code>mldsa.GenerateKey</code>のいずれかで鍵を作り、証明書を作る呼び出しは共通にしています。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-ukyanq-9" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ukyanq-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv)</span><br></pre></td></tr></table></figure></div>

<p><code>pub</code>と<code>priv</code>の型はアルゴリズムごとに違いますが、<code>CreateCertificate</code>の引数はどちらも<code>any</code>なので、呼び出し側の記述は変わりません。証明書（DER）の列は、<code>CreateCertificate</code>が返したバイト列の長さです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>アルゴリズム</th>
<th>方式</th>
<th>公開鍵</th>
<th>署名</th>
<th>証明書（DER）</th>
</tr>
</thead>
<tbody><tr>
<td>Ed25519</td>
<td>楕円曲線の離散対数、従来型</td>
<td>32バイト</td>
<td>64バイト</td>
<td>312バイト</td>
</tr>
<tr>
<td>ECDSA P-256</td>
<td>楕円曲線の離散対数、従来型</td>
<td>65バイト</td>
<td>約71バイト</td>
<td>約377バイト</td>
</tr>
<tr>
<td>RSA-2048</td>
<td>素因数分解、従来型</td>
<td>270バイト</td>
<td>256バイト</td>
<td>773バイト</td>
</tr>
<tr>
<td>ML-DSA-44</td>
<td>格子、ポスト量子</td>
<td>1312バイト</td>
<td>2420バイト</td>
<td>3973バイト</td>
</tr>
<tr>
<td>ML-DSA-65</td>
<td>格子、ポスト量子</td>
<td>1952バイト</td>
<td>3309バイト</td>
<td>5502バイト</td>
</tr>
<tr>
<td>ML-DSA-87</td>
<td>格子、ポスト量子</td>
<td>2592バイト</td>
<td>4627バイト</td>
<td>7460バイト</td>
</tr>
</tbody></table></div>
<p>ML-DSAの3行はいずれも、公開鍵と署名の合計に241バイトを足した値が証明書のサイズになっています。ECDSAの署名は符号化の都合で生成ごとに1〜2バイト前後するため、約を付けています。</p>
<p>ML-DSAの証明書はECDSA P-256の10倍から20倍です。このサイズは接続を張るときの速さに響きます。TLSでは接続のたびに、サーバーが自分の証明書と発行元のCA証明書をまとめてクライアントへ送ります。ML-DSA-65の証明書（5502バイト）が2枚3枚と重なると、暗号化通信が始まる前のやり取りだけで10キロバイトを超えるためです。</p>
<p>なお、古典アルゴリズムとポスト量子暗号を1つの署名にまとめるcomposite署名は、proposalで意図的に対象外とされました。移行期に両方の署名を出すなら、独立した2本の署名を作ります。</p>
<h2 id="まとめ">まとめ</h2><ul>
<li>いまの公開鍵暗号は、古典コンピュータでは現実的な時間で解けない計算問題に依拠しています。量子コンピュータが実用化すると、この前提が崩れて危殆化します</li>
<li>攻撃者はHarvest Now, Decrypt Laterを狙っています。暗号文を保存しておいて、後から解読する攻撃です。将来の量子コンピュータによる解読に備えて、今のうちに対策する必要があります</li>
<li>Goの鍵交換は1.24から対応済みです。相手が対応していれば、何もしなくてもポスト量子になっています。<code>ConnectionState.CurveID</code>で確認できます</li>
<li>1.27の<code>crypto/mldsa</code>で署名も作れるようになりました。署名は数十倍に膨らみますが、秘密鍵は32バイトで済みます</li>
<li>証明書とTLSもML-DSAで動きます。<code>generate_cert.go</code>の<code>--mldsa</code>で手元で試せます</li>
</ul>
<h2 id="参考">参考</h2><ul>
<li>Go 1.27リリース連載（インデックス）</li>
<li>Go 1.27 Release Notes</li>
<li>Go 1.24 Release Notes</li>
<li>crypto&#x2F;mldsa</li>
<li>crypto&#x2F;mlkem</li>
<li>proposal: crypto&#x2F;mldsa: new package · golang&#x2F;go#77626</li>
<li>proposal: crypto&#x2F;x509,crypto&#x2F;tls: add ML-DSA support · golang&#x2F;go#78888</li>
<li>FIPS 204: Module-Lattice-Based Digital Signature Standard</li>
<li>NIST IR 8547 (Initial Public Draft): Transition to Post-Quantum Cryptography Standards</li>
<li>Sigstore &amp; Post-Quantum Cryptography (2025)</li>
<li>Sigstore・cosign で「改ざんされていない」を検証した仕組み</li>
</ul>
]]></content>
    <summary type="html">Go 1.27でcrypto/mldsaが追加されました。ポスト量子暗号のデジタル署名ML-DSAの実装です。Goのポスト量子暗号への対応はGo 1.24から始まっています。鍵交換のML-KEMが入り、TLSの既定で有効になりました。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.27" scheme="https://future-architect.github.io/tags/Go1-27/"/>
    <category term="暗号" scheme="https://future-architect.github.io/tags/%E6%9A%97%E5%8F%B7/"/>
  </entry>
  <entry>
    <title>Go 1.27の構造体リテラルのキー拡張で、埋め込みフィールドの初期化が楽になった</title>
    <link href="https://future-architect.github.io/articles/20260805a/"/>
    <id>https://future-architect.github.io/articles/20260805a/</id>
    <published>2026-08-04T15:00:00.000Z</published>
    <updated>2026-08-04T15:00:00.000Z</updated>
    <author><name>真野隼記</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260805a/54ec55c6-cfaa-4442-b5cc-5456e49e5e03.jpg" alt="" width="1024" height="572">

<p>The Go gopher was designed by Renee French.</p>
<p>Go 1.27リリース連載の7本目です。</p>
<h2 id="はじめに">はじめに</h2><p>TIG真野です。</p>
<p>Go 1.27の言語仕様の変更は3つあり、そのうちの1つである「構造体リテラルのキー拡張」を紹介します。</p>
<p>検証は <code>go1.27rc2 linux/amd64</code> で行いました。</p>
<p>記事中の記載したコードサンプルのGo Playgroundも用意しました。</p>
<h2 id="何が変わったのか">何が変わったのか</h2><p>リリースノートには1文とあっさり、Issueはspec: direct reference to embedded fields in struct literals #9859です。まとめます。</p>
<ul>
<li>埋め込み構造体のフィールドを、構造体リテラルのキーに直接書けるようになりました。型 <code>E</code> を埋め込んだ型 <code>T</code> について、<code>T&#123;E: E&#123;A: 1&#125;&#125;</code> が <code>T&#123;A: 1&#125;</code> で済みます</li>
<li>ただし、埋め込み構造体がポインタの場合は、コンパイルエラーとなります</li>
</ul>
<p>仕様の変更もシンプルで、Composite literalsの節の<strong>1単語</strong>です。</p>
<ul>
<li>Go 1.26まで: The key is interpreted as a field <strong>name</strong> for struct literals, …</li>
<li>Go 1.27から: The key is interpreted as a field <strong>selector</strong> for struct literals, …</li>
</ul>
<p><code>name</code> が <code>selector</code> になっただけです。これで、キーの解決にセレクタの規則がそのまま適用されるようになります。</p>
<p>セレクタとは <code>u.Version</code> のようなフィールド参照のことです。specを読むと、「埋め込みをたどって最も浅い深さにあるものが選ばれる」「同じ深さに複数あれば不正」「他パッケージからは公開されたものだけ」といった規則が定められています。覚えるルールが少なくて済むのは筋が良さそうです。</p>
<h2 id="具体例：システム共通項目とテーブルドリブンテスト">具体例：システム共通項目とテーブルドリブンテスト</h2><p>弊社が扱うような業務システムで埋め込みを使う典型は、全テーブルに付いてくるシステム共通項目でしょう。登録日時・更新日時・バージョンといった項目を、テーブルごとに書きたくはないので、共通の構造体にまとめて埋め込みます。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1ofm483-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ofm483-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// SystemColumns は全テーブルに共通のシステム項目（登録者・更新者などは省略）</span></span><br><span class="line"><span class="keyword">type</span> SystemColumns <span class="keyword">struct</span> &#123;</span><br><span class="line">	CreatedAt time.Time</span><br><span class="line">	UpdatedAt time.Time</span><br><span class="line">	Version   <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// User はユーザーテーブルのレコード</span></span><br><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">	UserID <span class="type">string</span></span><br><span class="line">	Name   <span class="type">string</span></span><br><span class="line">	SystemColumns</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p><code>u.Version = 2</code> のように読み書きは昇格フィールドで直接できるのに、Go 1.26までは初期化のときだけ内側の型名を明示する必要がありました。Go 1.27ではフラットに書けます。</p>
<figure class="highlight diff_go"><table><tbody><tr><td class="code"><pre><span class="line"> u := User{</span><br><span class="line"> 	UserID:    <span>&quot;U0001&quot;</span>,</span><br><span class="line"> 	Name:      <span>&quot;mano&quot;</span>,</span><br><span class="line deletion">-	SystemColumns: SystemColumns{</span><br><span class="line deletion">-		CreatedAt: now,</span><br><span class="line deletion">-		UpdatedAt: now,</span><br><span class="line deletion">-		Version:   <span>1</span>,</span><br><span class="line deletion">-	},</span><br><span class="line addition">+	CreatedAt: now,</span><br><span class="line addition">+	UpdatedAt: now,</span><br><span class="line addition">+	Version:   <span>1</span>,</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure>

<p>Before側は、業務データが <code>UserID</code> と <code>Name</code> の2行だけなのに、システム共通項目の入れ子が場所を取ってしまっています。</p>
<p>個人的に一番効くと思ったのはテーブルドリブンテストです。テストケース構造体に共通部分を埋め込むと、ケースを1つ書くたびに <code>caseCommon: caseCommon&#123;...&#125;</code> というノイズが入り、ケースの本体が埋もれていました。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1ofm483-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ofm483-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> caseCommon <span class="keyword">struct</span> &#123;</span><br><span class="line">	name    <span class="type">string</span></span><br><span class="line">	tenant  <span class="type">string</span></span><br><span class="line">	wantErr <span class="type">bool</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> validateCase <span class="keyword">struct</span> &#123;</span><br><span class="line">	caseCommon</span><br><span class="line">	user User</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">cases := []validateCase&#123;</span><br><span class="line">	&#123;</span><br><span class="line">		name:    <span class="string">&quot;ok&quot;</span>, <span class="comment">// 1.26までは caseCommon: caseCommon&#123;name: &quot;ok&quot;, ...&#125; が必要</span></span><br><span class="line">		tenant:  <span class="string">&quot;future&quot;</span>,</span><br><span class="line">		wantErr: <span class="literal">false</span>,</span><br><span class="line">		user: User&#123;</span><br><span class="line">			UserID: <span class="string">&quot;U0001&quot;</span>, Name: <span class="string">&quot;mano&quot;</span>,</span><br><span class="line">			CreatedAt: now, UpdatedAt: now, Version: <span class="number">1</span>, <span class="comment">// 期待値のUserも同じくフラットに書ける</span></span><br><span class="line">		&#125;,</span><br><span class="line">	&#125;,</span><br><span class="line">	<span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h2 id="非公開型を埋め込んだ構造体を、他パッケージから初期化できるようになった">非公開型を埋め込んだ構造体を、他パッケージから初期化できるようになった</h2><p>同じパッケージ内なら <code>base: base&#123;ID: 1&#125;</code> と型名を書けるので従来も初期化できましたが、パッケージをまたぐとその型名を書けません。つまり他パッケージからはリテラルでの初期化手段そのものが無かったわけで、そこが解決します。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> lib</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> base <span class="keyword">struct</span> &#123;</span><br><span class="line">	ID   <span class="type">int</span></span><br><span class="line">	name <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Client <span class="keyword">struct</span> &#123;</span><br><span class="line">	base <span class="comment">// 非公開型の埋め込み</span></span><br><span class="line">	Endpoint <span class="type">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>これを利用する側のパッケージ（ここでは <code>app</code> とします）から <code>c.ID</code> の読み書きはできます。そのためGo 1.26までは、いったん作ってから別途代入する、という2段構えを強いられていました。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1ofm483-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ofm483-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> app <span class="comment">// lib をimportしている別パッケージ</span></span><br><span class="line"></span><br><span class="line">c := lib.Client&#123;Endpoint: <span class="string">&quot;https://example.com&quot;</span>&#125;</span><br><span class="line">c.ID = <span class="number">1</span> <span class="comment">// リテラルには書けないが、代入ならできる</span></span><br></pre></td></tr></table></figure></div>

<p>Go 1.27からは、初期化の時点で書けます。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1ofm483-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ofm483-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> app <span class="comment">// lib をimportしている別パッケージ</span></span><br><span class="line"></span><br><span class="line">c := lib.Client&#123;ID: <span class="number">1</span>, Endpoint: <span class="string">&quot;https://example.com&quot;</span>&#125; <span class="comment">// 1.27: OK</span></span><br><span class="line"><span class="comment">// ※1.26では 「unknown field ID in struct literal of type lib.Client」でコンパイルエラー</span></span><br></pre></td></tr></table></figure></div>

<p>非公開型を埋め込んで内部実装を隠しつつ設定値は公開するというライブラリでは、この2段構えから1行で済みます。初期化が楽になるのは嬉しい感じがします。</p>
<h2 id="細かいルールを確認する">細かいルールを確認する</h2><p>先述のセレクタの規則に則るなら、キーの解決もこうなるはずだ、という確認です。</p>
<ol>
<li><p>多段の埋め込み → OK</p>
 <div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1ofm483-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ofm483-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> Audit <span class="keyword">struct</span>&#123; CreatedBy <span class="type">string</span> &#125;</span><br><span class="line"><span class="keyword">type</span> SystemColumns <span class="keyword">struct</span> &#123;</span><br><span class="line">	Audit</span><br><span class="line">	Version <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">	Name <span class="type">string</span></span><br><span class="line">	SystemColumns</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">u := User&#123;Name: <span class="string">&quot;mano&quot;</span>, CreatedBy: <span class="string">&quot;batch&quot;</span>, Version: <span class="number">1</span>&#125; <span class="comment">// 2段たどった CreatedBy も書ける</span></span><br><span class="line"><span class="comment">// &#123;Name:mano SystemColumns:&#123;Audit:&#123;CreatedBy:batch&#125; Version:1&#125;&#125;</span></span><br></pre></td></tr></table></figure></div>
</li>
<li><p>深さが違う同名フィールド→浅い方が利用される</p>
 <figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> A <span class="keyword">struct</span>&#123; X <span class="type">int</span> &#125;</span><br><span class="line"><span class="keyword">type</span> B <span class="keyword">struct</span>&#123; A &#125;</span><br><span class="line"><span class="keyword">type</span> C <span class="keyword">struct</span> &#123;</span><br><span class="line">	B</span><br><span class="line">	X <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">c := C&#123;X: <span class="number">1</span>&#125; <span class="comment">// C.X = 1、C.B.A.X = 0</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>同じ深さで曖昧な場合 → エラー</p>
 <div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1ofm483-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ofm483-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> C <span class="keyword">struct</span>&#123; A; B &#125; <span class="comment">// AもBもフィールドXを持つ</span></span><br><span class="line">_ = C&#123;X: <span class="number">1</span>&#125;</span><br><span class="line"><span class="comment">// ./main.go:11:8: unknown field X in struct literal of type C</span></span><br></pre></td></tr></table></figure></div></li>
</ol>
<p>特段、挙動に違和感はないかと思います。</p>
<h2 id="Issue-9859、11年の議論">Issue #9859、11年の議論</h2><p>起票は2015年。「<code>T&#123;A: 1&#125;</code> と書けないのは冗長だし、フィールドに直接アクセスできる使用時とも非対称だ。許可できないか」という数行の内容でした。</p>
<p>しかし、 <code>T&#123;A: 1&#125;</code> と書けてしまうと、埋め込み先の型に後から <code>A</code> フィールドが追加されたときに、<strong>同じリテラルが別のフィールドを指すようになります</strong>（浅い方が勝つため）。冗長でも型名を書かせておく方が、そうした変更に対して頑健であるということから、当時のGoチームの温度感は低い雰囲気でした。</p>
<p>その後「任意の埋め込みを持つ構造体型 <code>T</code> について、<code>var x T; x.f1 = v1; ...</code> が有効なら、<code>T&#123;f1: v1, ...&#125;</code> と書けるべきだし、その逆もまた然り」という、1行ずつ代入できるならリテラルでも書けるべきだよねという理屈で議論が再開。</p>
<p>次の論点はポインタ埋め込み（<code>type T struct&#123; *E &#125;</code>）でした。値の埋め込みと違い、<code>*E</code> のゼロ値は <code>nil</code> です。つまり書き込む先の <code>E</code> が存在しないので、代入文で書いても実行時にパニックします。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1ofm483-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ofm483-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> E <span class="keyword">struct</span>&#123; A <span class="type">int</span> &#125;</span><br><span class="line"><span class="keyword">type</span> T <span class="keyword">struct</span>&#123; *E &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> t T</span><br><span class="line">t.A = <span class="number">1</span> <span class="comment">// panic: runtime error: invalid memory address or nil pointer dereference</span></span><br></pre></td></tr></table></figure></div>

<p><code>T&#123;A: 1&#125;</code> をリテラルで書けるようにするなら、この「書き込み先が無い」状況をどう扱うかを決めなければいけません。提示されたのは3つの選択肢（実質は2択）です。</p>
<ol>
<li>ポインタを暗黙的に確保する</li>
<li>実行時にnil参照でパニックする（※これは落選）</li>
<li>コンパイルエラーにする</li>
</ol>
<p>最終的には、規則がシンプルということで案3（コンパイルエラー）が選ばれました。</p>
<p>案1（暗黙的なアロケーション）も有力でしたが、次の2点がネックで選ばれませんでした。</p>
<ul>
<li>埋め込みポインタが連鎖していると、<code>x := Foo&#123;Value: v&#125;</code> が裏で <code>Foo&#123;Bar: &amp;Bar&#123;Baz: &amp;Baz&#123;Spam: &amp;Spam&#123;Egg: &amp;Egg&#123;Value: v&#125;&#125;&#125;&#125;&#125;</code> まで確保することになり、アロケーションが非自明になる</li>
<li>カプセル化が破れる。<code>type S struct&#123; *u &#125;</code> の場合、外部パッケージには <code>u</code> をアロケートする手段がこれまでありませんでした。もし案1を採用すると、呼び出し元で <code>lib.S&#123;A: 1&#125;</code> と書いた瞬間にコンパイラが暗黙に <code>u</code> をアロケートするため、コンストラクタを通らない「中身入りの <code>S</code>」を外部から作れてしまいます。これを避けようとすると「公開・非公開で挙動を分ける」という例外規則が必要で…とややこしいことになります</li>
</ul>
<p>後者の具体的に困るケースは例示がなく、私もよくわかりませんでした。しかし、細かいルールを追加して案1を通すより、シンプルな一律禁止のルールでまず進めることは、Goらしい判断だと思います。</p>
<p>というわけで、以下はコンパイルエラーになります。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1ofm483-8" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ofm483-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">	Name <span class="type">string</span></span><br><span class="line">	*SystemColumns <span class="comment">// ★ポインタ</span></span><br><span class="line">&#125;</span><br><span class="line">_ = User&#123;Name: <span class="string">&quot;子どもにせっかく甚平を買ったけど着てくれないの悲しい&quot;</span>, Version: <span class="number">1</span>&#125;</span><br><span class="line"><span class="comment">// ./main.go:10:25: invalid implicit pointer indirection to reach Version</span></span><br></pre></td></tr></table></figure></div>

<h2 id="FAQ">FAQ</h2><p><strong>Q. 昇格フィールドをキーに書くと、どの埋め込み由来か分からなくなりませんか？</strong></p>
<p>なります。どの埋め込み由来かを知りたくなったら、型定義に飛ぶしか無い認識です。</p>
<p><strong>Q. 後から外側の型に同名フィールドが追加されたら、リテラルの意味が変わる懸念は解決していないのでは？</strong></p>
<p>はい。 <code>SystemColumns.Version</code> のつもりで <code>User&#123;Version: 1&#125;</code> と書いていたコードに、後から <code>User.Version</code> が追加されると、キーの指す先が変わります。ただし同様のことが <code>u.Version = 1</code> という従来の代入文でも起きるので、リテラルに固有のリスクではないのです。そのために、今回の拡張が許容されたと理解しています。Go Playgroundにサンプルを置いておきます。</p>
<p><strong>Q. リフレクションや <code>encoding/json</code> の挙動は変わりますか？</strong></p>
<p>変わりません。構造体のメモリレイアウトもフィールドのタグも従来通りで、変わったのはリテラルの書き方だけです。<code>%+v</code> で出力すれば <code>&#123;UserID:U0001 Name:mano SystemColumns:&#123;CreatedAt:... UpdatedAt:... Version:1&#125;&#125;</code> と、これまで通り入れ子で表示されます。<code>json.Marshal</code> &#x2F; <code>json.Unmarshal</code> も影響を受けません。</p>
<h2 id="さいごに">さいごに</h2><p>Go 1.27の構造体リテラルのキー拡張を紹介しました。今回の修正は8&#x2F;7公開予定の記事で説明する予定の <code>go fix</code> で変換してくれます。</p>
<p>最後まで読んでいただき、ありがとうございました！</p>
]]></content>
    <summary type="html">Go 1.27の言語仕様の変更は3つあります。そのうちの1つである「構造体リテラルのキー拡張」を紹介します。簡単に言うと、埋め込み構造体のフィールドを、構造体リテラルのキーに直接書けるようになりました。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.27" scheme="https://future-architect.github.io/tags/Go1-27/"/>
  </entry>
  <entry>
    <title>Go 1.27 リリース連載： uuid</title>
    <link href="https://future-architect.github.io/articles/20260804a/"/>
    <id>https://future-architect.github.io/articles/20260804a/</id>
    <published>2026-08-03T15:00:00.000Z</published>
    <updated>2026-08-03T15:00:00.000Z</updated>
    <author><name>武田大輝</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260804a/top.jpg" alt="" width="720" height="393">

<p>The Go gopher was designed by Renee French.</p>
<p>Go 1.27 リリース連載 の 6 本目です。</p>
<h2 id="はじめに">はじめに</h2><p>本記事では Go 1.27 で標準ライブラリに追加された uuid パッケージを扱います。</p>
<p>パッケージの概要は、すでに Go 1.27 で標準ライブラリに追加される UUID パッケージ や Go の標準ライブラリに uuid パッケージが入る で解説されています。<br>本記事は API の紹介に加え、google&#x2F;uuid と比較した実測ベンチマークや移行などにも言及します。</p>
<h2 id="アップデートの概要">アップデートの概要</h2><p>リリースノート の記述はきわめて簡潔です。</p>
<blockquote>
<p>The new <code>uuid</code> package generates and parses UUIDs.</p>
</blockquote>
<p>パッケージ名は <code>crypto/uuid</code> ではなく <code>uuid</code> に落ち着きました。<br>生成できるのは UUIDv4 と UUIDv7 だけで、v1&#x2F;v2&#x2F;v3&#x2F;v5&#x2F;v6 の生成関数は入っていません。</p>
<p>主なディスカッションは次の Proposal と Change List を見ればわかります。</p>
<ul>
<li><p>Proposal<br>https://github.com/golang/go/issues/62026（2023 年起票、2026 年 4 月 8 日に Accept）<br>https://github.com/golang/go/issues/23789（2018 年に起票され、当時は見送られた最初の提案）</p>
</li>
<li><p>Change List<br>https://go-review.googlesource.com/c/go/+/725602</p>
</li>
</ul>
<p>API の全体像はこれだけです。かなり小さくまとまっています。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-b2xvb2-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-b2xvb2-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> uuid</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> UUID [<span class="number">16</span>]<span class="type">byte</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">New</span><span class="params">()</span></span> UUID</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewV4</span><span class="params">()</span></span> UUID</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewV7</span><span class="params">()</span></span> UUID</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Nil</span><span class="params">()</span></span> UUID</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Max</span><span class="params">()</span></span> UUID</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Parse</span><span class="params">(s <span class="type">string</span>)</span></span> (UUID, <span class="type">error</span>)</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">MustParse</span><span class="params">(s <span class="type">string</span>)</span></span> UUID</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u UUID)</span></span> String() <span class="type">string</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u UUID)</span></span> Compare(v UUID) <span class="type">int</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u UUID)</span></span> MarshalText() ([]<span class="type">byte</span>, <span class="type">error</span>)</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u UUID)</span></span> AppendText(b []<span class="type">byte</span>) ([]<span class="type">byte</span>, <span class="type">error</span>)</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(u *UUID)</span></span> UnmarshalText(b []<span class="type">byte</span>) <span class="type">error</span></span><br></pre></td></tr></table></figure></div>

<h2 id="そもそもの話">そもそもの話</h2><p>本題へ入る前に、UUID そのものと、標準ライブラリ入りに至った経緯を軽くおさらいします。</p>
<h3 id="UUID-とは">UUID とは</h3><p>UUID（Universally Unique Identifier）は 128 ビットの識別子です。<br>中央集権的な採番機構を用意しなくても、複数のノードがそれぞれ勝手に生成して衝突しない、という点が最大の価値です。</p>
<p>Go 1.27 の <code>uuid</code> パッケージが準拠するのは RFC 9562 で、これは 2024 年に RFC 4122 を置き換えた最新の仕様です。<br>テキスト表現は 8-4-4-4-12 のハイフン区切り小文字 16 進数で、<code>f81d4fae-7dec-11d0-a765-00a0c91e6bf6</code> のような形になります。</p>
<p>128 ビットのうち 6 ビットは、固定的な意味を持つフィールドに使われます。<br>4 ビットの version が生成アルゴリズムの種類を、2 ビットの variant がレイアウトの種類を表します。<br>テキスト表現で見ると、それぞれ 3 番目と 4 番目のグループの先頭の桁に現れます。</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">f81d4fae-7dec-11d0-a765-00a0c91e6bf6</span><br><span class="line">              ^    ^</span><br><span class="line">              |    +-- variant が入っている桁</span><br><span class="line">              +------- version が入っている桁</span><br></pre></td></tr></table></figure>

<p>ここで 16 進数の 1 桁は 4 ビットである点に注意が必要です。<br>version は 4 ビットをまるごと使いますが、variant は先頭の 2 ビットしか使いません。<br>上の例の該当桁を 2 進数へ展開すると、次のようになります。</p>
<div class="code-block"><figure class="highlight text"><input type="checkbox" id="code-wrap-b2xvb2-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-b2xvb2-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">version の桁 &quot;1&quot;</span><br><span class="line">    1 -&gt; 0001</span><br><span class="line">         ^^^^   4 ビットすべてが version 番号（0001 = 1 なので UUIDv1）</span><br><span class="line"></span><br><span class="line">variant の桁 &quot;a&quot;</span><br><span class="line">    a -&gt; 1010</span><br><span class="line">         ^^     先頭 2 ビットが variant（10 なので RFC 9562 のレイアウト）</span><br><span class="line">           ^^   残りの 2 ビットは UUID のデータの一部</span><br></pre></td></tr></table></figure></div>

<p>実際に使われるバージョンを並べておきます。<br>このほかに、実装が中身を自由に決められる v8 が実験・ベンダー独自用途として定義されています。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>バージョン</th>
<th>生成方法</th>
<th>時系列ソート</th>
<th>Go 1.27 での生成</th>
</tr>
</thead>
<tbody><tr>
<td>v1</td>
<td>時刻（100 ナノ秒単位）とノード ID（通常は MAC アドレス）</td>
<td>不可</td>
<td>非対応</td>
</tr>
<tr>
<td>v2</td>
<td>DCE Security。定義は RFC 9562 の範囲外</td>
<td>不可</td>
<td>非対応</td>
</tr>
<tr>
<td>v3</td>
<td>名前空間と名前の MD5 ハッシュ</td>
<td>不可</td>
<td>非対応</td>
</tr>
<tr>
<td>v4</td>
<td>122 ビットの乱数</td>
<td>不可</td>
<td><code>NewV4</code></td>
</tr>
<tr>
<td>v5</td>
<td>名前空間と名前の SHA-1 ハッシュ</td>
<td>不可</td>
<td>非対応</td>
</tr>
<tr>
<td>v6</td>
<td>v1 と同じ要素を時刻順に並べ替えたもの</td>
<td>可</td>
<td>非対応</td>
</tr>
<tr>
<td>v7</td>
<td>UNIX 時刻（ミリ秒）と乱数</td>
<td>可</td>
<td><code>NewV7</code></td>
</tr>
</tbody></table></div>
<p>「時系列ソート」は、パースせずにバイト列や文字列のまま並べ替えても時刻順になる、という意味です。</p>
<p>v1 も時刻を持っていますが、タイムスタンプが上位・中位・下位に分割して配置されているため、そのまま並べても時刻順にはなりません。v6 はこれを並べ替えて解消したもので、RFC 9562 はこの 2 つだけをソート可能なバージョンとして位置付けています。</p>
<h3 id="なぜいま標準ライブラリに入ったのか">なぜいま標準ライブラリに入ったのか</h3><p>UUID を標準ライブラリへ入れる提案は #23789 として 2018 年にすでに存在していましたが、「標準ライブラリに何が必要なのか情報が足りず、当面はサードパーティで十分」という結論で見送られていました。</p>
<p>その後 google&#x2F;uuid をはじめとするライブラリが広く使われるようになりました。</p>
<p>標準ライブラリへの導入の決め手は、次の 2 点でした。</p>
<p>1 つは相互運用性です。ライブラリが乱立していると、<code>UUID</code> 型もそれぞれ別物になります。<code>net/url</code> が URL の共通表現を提供しているのと同じように、標準の <code>uuid</code> があれば、どのライブラリとも UUID をそのまま受け渡せます。</p>
<p>もう 1 つは、2018 年の時点で足りないとされた「標準ライブラリに何が必要か」という情報が、実データとして得られるようになった点です。これだけ広く使われた結果、どの API がどれだけ使われているかを調べられるようになりました。</p>
<p>google&#x2F;uuid の利用箇所を 調査した結果 が次のものです。</p>
<div class="code-block"><figure class="highlight text"><input type="checkbox" id="code-wrap-b2xvb2-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-b2xvb2-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">function               usages   percentage   cumulative</span><br><span class="line">New                    14882    36.12        36.12</span><br><span class="line">UUID.String            14549    35.31        71.43</span><br><span class="line">NewString              3914     9.50         80.93</span><br><span class="line">Parse                  3280     7.96         88.89</span><br><span class="line">NewRandom              1548     3.76         92.64</span><br><span class="line">NewUUID                949      2.30         94.95</span><br><span class="line">MustParse              440      1.07         96.01</span><br><span class="line">NewV7                  263      0.64         96.65</span><br><span class="line">...</span><br><span class="line">UUID.Version           29       0.07         99.26</span><br><span class="line">UUID.Time              26       0.06         99.39</span><br></pre></td></tr></table></figure></div>

<p>上位 4 つ（生成・文字列化・パース）で全体の約 89% を占め、バージョンや時刻を取り出す introspection 系はほとんど使われていません。同時は判断できなかった「何を入れるべきか」が明確になったわけです。</p>
<h2 id="使ってみる">使ってみる</h2><h3 id="生成">生成</h3><p>新しい UUID をつくる関数は <code>New</code>、<code>NewV4</code>、<code>NewV7</code> の 3 つです。<br>アルゴリズムにこだわりがなければ <code>New</code>、明示したいときは <code>NewV4</code> か <code>NewV7</code> を呼びます。<br>これとは別に、RFC 9562 が定義する固定値を返す <code>Nil</code> と <code>Max</code> があります。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;uuid&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	fmt.Println(<span class="string">&quot;New:  &quot;</span>, uuid.New())</span><br><span class="line">	fmt.Println(<span class="string">&quot;NewV4:&quot;</span>, uuid.NewV4())</span><br><span class="line">	fmt.Println(<span class="string">&quot;NewV7:&quot;</span>, uuid.NewV7())</span><br><span class="line">	fmt.Println(<span class="string">&quot;Nil:  &quot;</span>, uuid.Nil())</span><br><span class="line">	fmt.Println(<span class="string">&quot;Max:  &quot;</span>, uuid.Max())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go run ./gen</span></span><br><span class="line">New:   5db4b40e-a8d5-4d10-8838-d7a351f436b2</span><br><span class="line">NewV4: b77b0c2f-85f9-48cd-a996-6e8dccf6de6f</span><br><span class="line">NewV7: 019fc17a-5977-79cd-ada3-7a04de777038</span><br><span class="line">Nil:   00000000-0000-0000-0000-000000000000</span><br><span class="line">Max:   ffffffff-ffff-ffff-ffff-ffffffffffff</span><br></pre></td></tr></table></figure>

<p>どれもエラーを返しません。</p>
<p>乱数の取得に <code>crypto/rand.Read</code> を直接使っており、この関数は Go 1.24 以降エラーを返さない仕様（失敗時はプロセスをクラッシュさせる）になっているためです。</p>
<p>google&#x2F;uuid の <code>NewRandom</code> がエラーを返していたのは、この仕様変更より前のなごりです。</p>
<h3 id="パース">パース</h3><p><code>Parse</code> が受け付ける表記は 4 種類あります。</p>
<p>ハイフン区切りの標準形に加えて、波括弧付き、URN 形式、ハイフンなしの 32 文字が通ります。</p>
<p>16 進数の英字は大文字と小文字のどちらでもかまいません。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-b2xvb2-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-b2xvb2-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> _, s := <span class="keyword">range</span> []<span class="type">string</span>&#123;</span><br><span class="line">		<span class="string">&quot;f81d4fae-7dec-11d0-a765-00a0c91e6bf6&quot;</span>,</span><br><span class="line">		<span class="string">&quot;&#123;f81d4fae-7dec-11d0-a765-00a0c91e6bf6&#125;&quot;</span>,</span><br><span class="line">		<span class="string">&quot;urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6&quot;</span>,</span><br><span class="line">		<span class="string">&quot;f81d4fae7dec11d0a76500a0c91e6bf6&quot;</span>,</span><br><span class="line">		<span class="string">&quot;F81D4FAE-7DEC-11D0-A765-00A0C91E6BF6&quot;</span>,</span><br><span class="line">		<span class="string">&quot;f81d4fae-7dec-11d0-a765-00a0c91e6bf&quot;</span>,</span><br><span class="line">	&#125; &#123;</span><br><span class="line">		u, err := uuid.Parse(s)</span><br><span class="line">		fmt.Printf(<span class="string">&quot;%-46s -&gt; %v %v\n&quot;</span>, s, u, err)</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 定数など確実に成功する入力には MustParse を使う</span></span><br><span class="line">	u := uuid.MustParse(<span class="string">&quot;f81d4fae-7dec-11d0-a765-00a0c91e6bf6&quot;</span>)</span><br><span class="line">	fmt.Println(<span class="string">&quot;MustParse:&quot;</span>, u)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-b2xvb2-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-b2xvb2-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go run ./parse</span></span><br><span class="line">f81d4fae-7dec-11d0-a765-00a0c91e6bf6           -&gt; f81d4fae-7dec-11d0-a765-00a0c91e6bf6 &lt;nil&gt;</span><br><span class="line">&#123;f81d4fae-7dec-11d0-a765-00a0c91e6bf6&#125;         -&gt; f81d4fae-7dec-11d0-a765-00a0c91e6bf6 &lt;nil&gt;</span><br><span class="line">urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6  -&gt; f81d4fae-7dec-11d0-a765-00a0c91e6bf6 &lt;nil&gt;</span><br><span class="line">f81d4fae7dec11d0a76500a0c91e6bf6               -&gt; f81d4fae-7dec-11d0-a765-00a0c91e6bf6 &lt;nil&gt;</span><br><span class="line">F81D4FAE-7DEC-11D0-A765-00A0C91E6BF6           -&gt; f81d4fae-7dec-11d0-a765-00a0c91e6bf6 &lt;nil&gt;</span><br><span class="line">f81d4fae-7dec-11d0-a765-00a0c91e6bf            -&gt; 00000000-0000-0000-0000-000000000000 invalid uuid</span><br><span class="line">MustParse: f81d4fae-7dec-11d0-a765-00a0c91e6bf6</span><br></pre></td></tr></table></figure></div>

<p>この緩さは意図的なものです。</p>
<p>google&#x2F;uuid と satori&#x2F;go.uuid が受け付ける表記をすべて受け入れることで、移行時に挙動が変わらないようにしています。厳密に検証したい場合は <code>Parse</code> へ渡す前に自分で長さをチェックする、というのが Proposal での 結論 でした。</p>
<p>エラーの内容は <code>invalid uuid</code> の 1 種類だけです。google&#x2F;uuid のように長さ違いと文字違いでエラー型を分けるようなことはしていません。</p>
<h3 id="比較とソート">比較とソート</h3><p><code>UUID</code> の実体は <code>[16]byte</code> であり、<code>==</code> で比較でき、map のキーとしてもそのまま使えます。</p>
<p>順序が必要な場面では <code>Compare</code> メソッドを使います。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-b2xvb2-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-b2xvb2-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	a := uuid.MustParse(<span class="string">&quot;f81d4fae-7dec-11d0-a765-00a0c91e6bf6&quot;</span>)</span><br><span class="line">	b := uuid.MustParse(<span class="string">&quot;F81D4FAE7DEC11D0A76500A0C91E6BF6&quot;</span>)</span><br><span class="line">	fmt.Println(<span class="string">&quot;a == b:&quot;</span>, a == b)</span><br><span class="line">	fmt.Println(<span class="string">&quot;map key:&quot;</span>, <span class="keyword">map</span>[uuid.UUID]<span class="type">string</span>&#123;a: <span class="string">&quot;hello&quot;</span>&#125;[b])</span><br><span class="line"></span><br><span class="line">	ids := []uuid.UUID&#123;uuid.Max(), uuid.Nil(), a&#125;</span><br><span class="line">	slices.SortFunc(ids, uuid.UUID.Compare)</span><br><span class="line">	fmt.Println(<span class="string">&quot;sorted: &quot;</span>, ids)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-b2xvb2-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-b2xvb2-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go run ./cmp</span></span><br><span class="line">a == b: true</span><br><span class="line">map key: hello</span><br><span class="line">sorted:  [00000000-0000-0000-0000-000000000000 f81d4fae-7dec-11d0-a765-00a0c91e6bf6 ffffffff-ffff-ffff-ffff-ffffffffffff]</span><br></pre></td></tr></table></figure></div>

<p><code>slices.SortFunc(ids, uuid.UUID.Compare)</code> のようにメソッド式を渡せば、比較関数を書く必要もありません。</p>
<p>並び順は RFC 9562 が定めるビッグエンディアンのバイト順で、時刻順に意味を持つのは v6 と v7 だけである点には注意してください。</p>
<h3 id="JSON-でのやりとり">JSON でのやりとり</h3><p><code>UUID</code> は <code>MarshalText</code> &#x2F; <code>UnmarshalText</code> を実装しているため、<code>encoding/json</code> では自動的に文字列として扱われます。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-b2xvb2-8" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-b2xvb2-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">	ID   uuid.UUID <span class="string">`json:&quot;id&quot;`</span></span><br><span class="line">	Name <span class="type">string</span>    <span class="string">`json:&quot;name&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	u := User&#123;ID: uuid.MustParse(<span class="string">&quot;d556ac56-0e8d-4a30-b7b0-647fe7a22aba&quot;</span>), Name: <span class="string">&quot;Bob&quot;</span>&#125;</span><br><span class="line">	b, _ := json.Marshal(u)</span><br><span class="line">	fmt.Println(<span class="string">&quot;marshal:&quot;</span>, <span class="type">string</span>(b))</span><br><span class="line"></span><br><span class="line">	<span class="keyword">var</span> back User</span><br><span class="line">	_ = json.Unmarshal([]<span class="type">byte</span>(<span class="string">`&#123;&quot;id&quot;:&quot;&#123;D556AC56-0E8D-4A30-B7B0-647FE7A22ABA&#125;&quot;,&quot;name&quot;:&quot;Bob&quot;&#125;`</span>), &amp;back)</span><br><span class="line">	fmt.Printf(<span class="string">&quot;unmarshal: %v 一致=%v\n&quot;</span>, back.ID, back.ID == u.ID)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-b2xvb2-9" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-b2xvb2-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go run ./jsondemo</span></span><br><span class="line">marshal: &#123;&quot;id&quot;:&quot;d556ac56-0e8d-4a30-b7b0-647fe7a22aba&quot;,&quot;name&quot;:&quot;Bob&quot;&#125;</span><br><span class="line">unmarshal: d556ac56-0e8d-4a30-b7b0-647fe7a22aba 一致=true</span><br></pre></td></tr></table></figure></div>

<p>注意したいのは、<code>UnmarshalText</code> が <code>Parse</code> と同じ 4 種類の表記を受け付ける点です。</p>
<p>上の例のように、波括弧付きで大文字の JSON が何のエラーもなく通ります。</p>
<p>外部から受け取る JSON を厳密に検証したい場合は、<code>len(s) == 36</code> のような長さチェックを自分で足す必要があります。</p>
<h2 id="API-の設計思想を-Proposal-から読む">API の設計思想を Proposal から読む</h2><p>Go 1.27 の <code>uuid</code> パッケージが驚くほど小さいのは、先ほどの利用実績をそのまま API 設計へ反映したためです。</p>
<p>その判断の過程は Proposal に残っています。#62026 には 300 件を超えるコメントが付き、Accept 時には実装者である neild 氏が 設計判断の根拠をまとめたコメント を残しました。</p>
<p>ここからいくつか拾ってみます。</p>
<h3 id="なぜ-crypto-uuid-ではなく-uuid-なのか">なぜ <code>crypto/uuid</code> ではなく <code>uuid</code> なのか</h3><p>当初の提案は <code>crypto/uuid</code> でした。<code>crypto/rand</code> の安全な乱数を使う点を名前で示す意図があったためです。</p>
<p>しかし RFC 9562 に暗号学的な用語がほとんど登場せず、暗号強度は「安全な乱数ソースを推奨する」という文脈でしか扱われません。</p>
<p><code>gofrs/uuid</code> を保守している dylan-bourque 氏の この主張 が決め手となり、より短い <code>uuid</code> が選ばれました。</p>
<h3 id="なぜ-16-byte-なのか">なぜ <code>[16]byte</code> なのか</h3><p>不透明な構造体にする案もありましたが、既存パッケージのほぼすべてが <code>[16]byte</code> を採用しているため、同じ表現にしておけば単なる型変換で相互変換できるという利点が決め手になりました。</p>
<p>不正な UUID を表す <code>[16]byte</code> はどうするのかという問いには、RFC 9562 が有効性という概念を定義していない、つまり不正な UUID というものは存在しない、とプロポーザルでも回答されています。</p>
<p>現行のどのアルゴリズムでも生成されない 16 バイト値は存在しますが、それは不正であることを意味しません。</p>
<h3 id="なぜ-New-と-NewV4-の両方があるのか">なぜ <code>New</code> と <code>NewV4</code> の両方があるのか</h3><p>現時点で両者の挙動は同一です。</p>
<p>それでも両方あるのは、呼び出し側の意図を表現できるからだと説明されています。<code>NewV4</code> を呼ぶのは「UUIDv4 が欲しい」という宣言であり、<code>New</code> を呼ぶのは「UUID が欲しいが生成アルゴリズムにはこだわらない」という宣言です。将来より良いデフォルトが現れたとき、<code>New</code> の利用者だけを移行させられます。</p>
<h3 id="なぜデフォルトが-v7-ではなく-v4-なのか">なぜデフォルトが v7 ではなく v4 なのか</h3><p>UUIDv7 は時刻順に並ぶため、B-tree インデックスへの大量挿入ではたしかに有利です。</p>
<p>それでも v4 がデフォルトに選ばれたのには、次の理由があります。</p>
<ul>
<li>Cassandra、CockroachDB、Spanner のように水平シャーディングされたデータストアでは、時刻順に並ぶ ID は書き込み先が特定のノードへ偏り、ホットスポットになる</li>
<li>v7 には生成時刻が埋め込まれるため、ID を外部へ公開していると作成日時が読み取れる</li>
</ul>
<p>v4 は単なる乱数なので性能やセキュリティ上の懸念がなく「最も安全なデフォルト」だと結論づけられています。</p>
<h3 id="なぜ-NewRandom-ではなく-NewV4-なのか">なぜ <code>NewRandom</code> ではなく <code>NewV4</code> なのか</h3><p><code>NewRandom</code> のほうが説明的ではありますが UUIDv7 に対応する命名が困難です。</p>
<p><code>NewSequential</code> や <code>NewMonotonic</code> という候補は、v6 と v7 の両方へ等しく当てはまってしまいます。v4 だけ説明的な名前にするのは不整合ですし、<code>NewRandom</code> という名前が将来にわたって一意な意味を保つ保証もありません。</p>
<p>結果としてバージョン番号で命名する方式が採用されました。</p>
<h3 id="Nil-と-Max-が変数から関数になった経緯"><code>Nil</code> と <code>Max</code> が変数から関数になった経緯</h3><p>google&#x2F;uuid は <code>var Nil</code> という公開変数として定義しており、互換性を重視するなら変数にすべきという意見と、書き換えられる公開変数は避けるべきという意見が対立しました。</p>
<p>Proposal レビュー側は当初「実害の証拠があれば関数にする」というスタンスでしたが、実際に google&#x2F;uuid の変数が意図せず書き換えられて問題になった事例 が提示されたことで、最終的に関数へ倒れました。</p>
<p>なお <code>uuid.UUID</code> のゼロ値は Nil UUID そのものですので、実用上は <code>id == uuid.UUID&#123;&#125;</code> でも <code>id == uuid.Nil()</code> でも同じです。</p>
<h3 id="採用されなかったもの">採用されなかったもの</h3><p>Go の互換性保証のもとでは、一度公開した API は取り下げられません。そのため「迷ったら入れない」方針が徹底されており、次のものが見送られました。</p>
<p>1 つめは v1&#x2F;v2&#x2F;v3&#x2F;v5&#x2F;v6 の生成です。生成関数の利用実績を 集計 すると v4 が 94.17%、v1 が 4.39%、v7 が 1.22% で、残りは合計 0.22% でした。v1 利用の大半は <code>NewUUID</code> という紛らわしい名前に手が伸びただけと推測されています。v3&#x2F;v5 は MD5&#x2F;SHA-1 という壊れたハッシュに依存しており、新規に決定的な UUID が必要なら、自由形式の v8 で現代的なハッシュを使うほうがよい、という整理です。</p>
<p>2 つめは <code>Version()</code> や <code>Time()</code> といった introspection です。利用実績がほとんどなく、Google 社内のコードベースを調べても <code>Version</code> の利用箇所の大半は「バージョン 4 以外を理由もなく弾く」という誤用だった、と報告されています。</p>
<p>3 つめは UUIDv7 の生成時刻を指定する API です。時刻を取り出す手段を提供しないと決めた以上、設定だけできるのは不自然という理由です。「時刻漏洩を避けるためにランダムなオフセットを入れるべき」という議論もありましたが、一貫したオフセットは推測可能で、可変にすると v7 の性能上の利点が失われます。</p>
<p>4 つめは <code>Generator</code> 型による乱数ソースの差し替えです。インジェクションしたいなら <code>func() uuid.UUID</code> を渡せば十分、という結論になりました。</p>
<h2 id="ベンチマーク">ベンチマーク</h2><p>google&#x2F;uuid v1.6.0 と比較したベンチマークを取りました。</p>
<details>
<summary>bench/bench_test.go</summary>

<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> bench</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;runtime&quot;</span></span><br><span class="line">	<span class="string">&quot;testing&quot;</span></span><br><span class="line">	<span class="string">&quot;uuid&quot;</span></span><br><span class="line"></span><br><span class="line">	guuid <span class="string">&quot;github.com/google/uuid&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> s = <span class="string">&quot;f81d4fae-7dec-11d0-a765-00a0c91e6bf6&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkNewV4</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> b.Loop() &#123;</span><br><span class="line">		uuid.NewV4()</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkGoogleNewV4</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> b.Loop() &#123;</span><br><span class="line">		guuid.New()</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkNewV4Parallel</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">	b.RunParallel(<span class="function"><span class="keyword">func</span><span class="params">(pb *testing.PB)</span></span> &#123;</span><br><span class="line">		<span class="keyword">var</span> u uuid.UUID</span><br><span class="line">		<span class="keyword">for</span> pb.Next() &#123;</span><br><span class="line">			u = uuid.NewV4()</span><br><span class="line">		&#125;</span><br><span class="line">		runtime.KeepAlive(u)</span><br><span class="line">	&#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkNewV7</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> b.Loop() &#123;</span><br><span class="line">		uuid.NewV7()</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkGoogleNewV7</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> b.Loop() &#123;</span><br><span class="line">		guuid.NewV7()</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkNewV7Parallel</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">	b.RunParallel(<span class="function"><span class="keyword">func</span><span class="params">(pb *testing.PB)</span></span> &#123;</span><br><span class="line">		<span class="keyword">var</span> u uuid.UUID</span><br><span class="line">		<span class="keyword">for</span> pb.Next() &#123;</span><br><span class="line">			u = uuid.NewV7()</span><br><span class="line">		&#125;</span><br><span class="line">		runtime.KeepAlive(u)</span><br><span class="line">	&#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkParse</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> b.Loop() &#123;</span><br><span class="line">		uuid.Parse(s)</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkGoogleParse</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> b.Loop() &#123;</span><br><span class="line">		guuid.Parse(s)</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkString</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">	u := uuid.MustParse(s)</span><br><span class="line">	<span class="keyword">for</span> b.Loop() &#123;</span><br><span class="line">		_ = u.String()</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkGoogleString</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">	u := guuid.MustParse(s)</span><br><span class="line">	<span class="keyword">for</span> b.Loop() &#123;</span><br><span class="line">		_ = u.String()</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

</details>

<p>環境は <code>go1.27rc2</code> &#x2F; linux&#x2F;arm64 &#x2F; 12 コアです。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-b2xvb2-10" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-b2xvb2-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go <span class="built_in">test</span> -run=NONE -bench=. -benchmem ./bench</span></span><br><span class="line">goos: linux</span><br><span class="line">goarch: arm64</span><br><span class="line">BenchmarkNewV4-12            	13977332	        84.19 ns/op	       0 B/op	       0 allocs/op</span><br><span class="line">BenchmarkGoogleNewV4-12      	13290716	        89.40 ns/op	      16 B/op	       1 allocs/op</span><br><span class="line">BenchmarkNewV4Parallel-12    	141339780	         8.456 ns/op	       0 B/op	       0 allocs/op</span><br><span class="line">BenchmarkNewV7-12            	12154754	        98.62 ns/op	       0 B/op	       0 allocs/op</span><br><span class="line">BenchmarkGoogleNewV7-12      	 7964306	       149.1 ns/op	      16 B/op	       1 allocs/op</span><br><span class="line">BenchmarkNewV7Parallel-12    	 5942059	       204.8 ns/op	       0 B/op	       0 allocs/op</span><br><span class="line">BenchmarkParse-12            	48857696	        24.34 ns/op	       0 B/op	       0 allocs/op</span><br><span class="line">BenchmarkGoogleParse-12      	63914497	        19.05 ns/op	       0 B/op	       0 allocs/op</span><br><span class="line">BenchmarkString-12           	36801724	        31.34 ns/op	      48 B/op	       1 allocs/op</span><br><span class="line">BenchmarkGoogleString-12     	41646963	        28.32 ns/op	      48 B/op	       1 allocs/op</span><br></pre></td></tr></table></figure></div>

<p>いくつかポイントを説明します。</p>
<p>まずは、生成側のアロケーションがゼロになっている点です。google&#x2F;uuid は <code>io.ReadFull(rander, uuid[:])</code> のようにインターフェース越しに配列を渡すため 16 バイトのヒープアロケーションが発生しますが、標準ライブラリは <code>crypto/rand.Read</code> を直接呼ぶのでエスケープしません。この差もあって、v7 では 1.5 倍の性能差がでています。</p>
<p>意外だったのは <code>NewV7</code> を並列化したときの性能です。<code>NewV4</code> は 12 コアで 84ns から 8.5ns へとほぼコア数分だけ速くなるのに、<code>NewV7</code> は 99ns から 205ns へ悪化しました。<code>NewV7</code> は単調増加を保証するためにパッケージレベルで採番を直列化しており、これがそのままボトルネックになります。とはいえ 1 秒あたり約 500 万件は生成できる計算ですので、実際にこれが問題になるのは相当に特殊なワークロードだけでしょう。</p>
<p>逆に <code>Parse</code> と <code>String</code> は、わずかながら google&#x2F;uuid のほうが速いという結果になりました。標準ライブラリ版は <code>Parse</code> を <code>UnmarshalText</code> 経由で実装しているぶん、専用に最適化された実装には劣るのではないかと考えられます。差は数ナノ秒ですので、実用上は誤差の範囲です。</p>
<h2 id="google-uuid-からの移行">google&#x2F;uuid からの移行</h2><p>型が同じ <code>[16]byte</code> ですので、移行そのものは難しくありません。</p>
<p>主な対応関係を表にまとめました。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>google&#x2F;uuid</th>
<th>標準ライブラリ <code>uuid</code></th>
<th>備考</th>
</tr>
</thead>
<tbody><tr>
<td><code>uuid.New()</code></td>
<td><code>uuid.New()</code></td>
<td></td>
</tr>
<tr>
<td><code>uuid.NewString()</code></td>
<td><code>uuid.New().String()</code></td>
<td></td>
</tr>
<tr>
<td><code>uuid.NewRandom()</code></td>
<td><code>uuid.NewV4()</code></td>
<td>標準はエラーを返さない</td>
</tr>
<tr>
<td><code>uuid.NewV7()</code></td>
<td><code>uuid.NewV7()</code></td>
<td>標準はエラーを返さない</td>
</tr>
<tr>
<td><code>uuid.NewUUID()</code></td>
<td>なし</td>
<td></td>
</tr>
<tr>
<td><code>uuid.NewMD5</code> &#x2F; <code>NewSHA1</code></td>
<td>なし</td>
<td></td>
</tr>
<tr>
<td><code>uuid.Parse()</code></td>
<td><code>uuid.Parse()</code></td>
<td></td>
</tr>
<tr>
<td><code>uuid.ParseBytes()</code></td>
<td><code>u.UnmarshalText(b)</code></td>
<td></td>
</tr>
<tr>
<td><code>uuid.MustParse()</code></td>
<td><code>uuid.MustParse()</code></td>
<td></td>
</tr>
<tr>
<td><code>uuid.Validate()</code></td>
<td>なし</td>
<td><code>Parse</code> のエラーで判定する</td>
</tr>
<tr>
<td><code>uuid.Nil</code></td>
<td><code>uuid.Nil()</code></td>
<td></td>
</tr>
<tr>
<td><code>uuid.Max</code></td>
<td><code>uuid.Max()</code></td>
<td></td>
</tr>
<tr>
<td><code>u.Version()</code></td>
<td>なし</td>
<td>必要なら <code>u[6]&gt;&gt;4</code> を自前で書く</td>
</tr>
<tr>
<td><code>u.Time()</code></td>
<td>なし</td>
<td></td>
</tr>
<tr>
<td><code>u.MarshalBinary()</code></td>
<td><code>u[:]</code></td>
<td>実体が配列なのでスライス化するだけ</td>
</tr>
<tr>
<td><code>u.Scan()</code> &#x2F; <code>u.Value()</code></td>
<td>なし</td>
<td><code>database/sql</code> 側が uuid.UUID を認識する</td>
</tr>
</tbody></table></div>
<h2 id="おわりに">おわりに</h2><p>300 件を超える議論の末に残ったのが 12 個の関数とメソッドだけ、というのは、いかにも Go らしい結論だと思います。</p>
<p>標準で使えるようになったのは非常にうれしいアップデートですね。</p>
<h2 id="参考">参考</h2><ul>
<li>Go 1.27 で標準ライブラリに追加される UUID パッケージ</li>
<li>Go の標準ライブラリに uuid パッケージが入る</li>
</ul>
]]></content>
    <summary type="html">Go 1.27 で標準ライブラリに追加されたuuidパッケージを扱います。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.27" scheme="https://future-architect.github.io/tags/Go1-27/"/>
    <category term="UUID" scheme="https://future-architect.github.io/tags/UUID/"/>
  </entry>
  <entry>
    <title>Go 1.27リリース連載：goroutineleakプロファイルでリークを検出して修正する</title>
    <link href="https://future-architect.github.io/articles/20260803a/"/>
    <id>https://future-architect.github.io/articles/20260803a/</id>
    <published>2026-08-02T15:00:00.000Z</published>
    <updated>2026-08-02T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260803a/top.jpg" alt="" width="720" height="402">

<p>The Go gopher was designed by Renee French.</p>
<p>Go 1.27リリース連載の5本目です。</p>
<h2 id="はじめに">はじめに</h2><p>Go 1.26で実験的に導入されたgoroutineリーク検出プロファイル<code>goroutineleak</code>が、Go 1.27で正式機能になりました。</p>
<p>goroutineリークの基本原理や、従来手法（goleakなど）との比較は、Go 1.26 リリース連載 Goroutine Leak Profilesで解説いただいています。今回は、正式化後の使い方と、検出したリークをどう修正するかを中心に紹介します。あわせて、検出の土台になった論文と、Goに取り込まれた範囲にも触れます。</p>
<h2 id="Go-1-27での変更">Go 1.27での変更</h2><p>Go 1.26では<code>goroutineleak</code>は実験的機能であり、ビルド時に次の指定が必要でした（Go 1.26リリースノート）。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">GOEXPERIMENT=goroutineleakprofile go build</span><br></pre></td></tr></table></figure>

<p>Go 1.27では正式機能（generally available）になり、実験フラグなしで利用できます。<code>goroutineleakprofile</code>という<code>GOEXPERIMENT</code>設定自体も削除されました。</p>
<p>利用箇所は次の2つです。</p>
<ul>
<li><code>runtime/pprof</code>の<code>goroutineleak</code>プロファイル</li>
<li><code>net/http/pprof</code>の<code>/debug/pprof/goroutineleak</code>エンドポイント</li>
</ul>
<h2 id="検出の仕組み">検出の仕組み</h2><p>goroutineリークとは、goroutineがチャネルなどの同期プリミティブで待機したまま、その待機を解除する手段が失われた状態です。プログラム全体が停止する通常のデッドロックとは異なり、一部のgoroutineだけが残るため、発見しにくい問題です。なお、待機中であること自体はリークではありません。I&#x2F;OやHTTP接続を待つgoroutineのように、将来再開できるものは正常です。</p>
<p>正常な待機・goroutineリーク・通常のデッドロックの関係を整理すると、次のとおりです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>状態</th>
<th>待機の解除経路</th>
<th>プログラム全体</th>
</tr>
</thead>
<tbody><tr>
<td>正常な待機（I&#x2F;OやHTTP応答待ちなど）</td>
<td>残っている（いずれ再開する）</td>
<td>動き続ける</td>
</tr>
<tr>
<td>goroutineリーク</td>
<td>失われている（永久に再開しない）</td>
<td>動き続けるため、発見しにくい</td>
</tr>
<tr>
<td>通常のデッドロック</td>
<td>失われている</td>
<td>全体が停止するため、すぐ気づく</td>
</tr>
</tbody></table></div>
<p>発見しにくい理由は、リークしていない部分が正常に動き続けることにあります。アプリケーションは応答を返し続ける一方で、リークしたgoroutineはスタックを保持したまま残り、そこから参照されるチャネルや変数もGCの視点では到達可能なため、回収されません。リークが蓄積するほど解放されないメモリが増え、GCがマークすべきオブジェクトも増えるため、メモリ使用量とGC負荷が少しずつ上がっていきます。症状がメモリ使用量の緩やかな増加などとして遅れて現れるうえ、再起動すると一時的に解消してしまうため、原因の特定が難しくなります。</p>
<p><code>goroutineleak</code>は、GCの到達可能性解析を「そのgoroutineは再開できるか」という判定に応用します（Proposal、Design Document）。goroutineがチャネルで受信待ちしているとき、実行可能なgoroutine（今後実行可能になり得るものを含みます）のどれからもそのチャネルへ到達できなければ、待機を解除するコードは二度と実行されません。これをリークと判定します。検出専用のGCサイクルはプロファイル取得時にだけ実行され、利用しない間は追加の実行時オーバーヘッドが生じないように設計されています。</p>
<pre class="mermaid" data-mermaid="1ddcb17e8577cece8a383d4cab3263ad5435dfe7a5c7650e7caed0c1d8dfed8e">flowchart TB
    runnable["実行可能なgoroutine"]
    ch["channel"]
    blocked["blocked goroutine"]
    leaked["leaked"]

    runnable -.->|"到達不能"| ch
    blocked -->|"受信待ち"| ch
    blocked ==>|"リークと判定"| leaked</pre>

<p>この検出は、偽陽性（誤検出）を出さないことを重視して設計されています。一方で、すべてのリークを検出できるわけではありません。グローバル変数や、実行可能なgoroutineのローカル変数から到達可能なチャネルで待機している場合、実際には誰も解除しなくても「到達可能」であるため、検出されないことがあります。偽陰性（見逃し）はあり得るため、プロファイルの検出件数が0件（後述する<code>debug=1</code>出力では<code>total 0</code>と表示されます）であっても、リークが存在しないことの証明にはなりません。</p>
<p>なお、GCのマーキングの仕組みを詳しく知りたい方はGo 1.26の新GC「Green Tea（緑茶）」解説もあわせてご覧ください。ただし、Green Teaはマーキングを高速化するGCの最適化であり、goroutineリーク検出とは別の機能です。</p>
<h3 id="論文からGoへの取り込み">論文からGoへの取り込み</h3><p>この検出理論は、Saiocらによる論文「Dynamic Partial Deadlock Detection and Recovery via Garbage Collection」（ASPLOS 2025）で提案されました。論文では、プログラム全体ではなく一部の並行処理だけが永久にブロックされる状態をpartial deadlock（部分デッドロック）と呼んでいます。Goでは、このような状態は一般にgoroutine leakとも呼ばれます。</p>
<p>検出では、実行中または実行可能なgoroutineを起点としてGCのマークを開始します。そこから到達可能な並行処理プリミティブによって再開し得るgoroutineを新たにliveとみなし、そのスタックからさらにマークを続けます。この処理を固定点まで繰り返し、最後までliveと判定されなかったgoroutineをpartial deadlockとします。</p>
<p>研究実装はGolf（Goroutine Leak Fixer）と呼ばれ、検出にとどまらず、リークしたgoroutineを停止させ、スタックやそこからのみ到達可能なオブジェクトをGCの回収対象にすることまで扱っていました。ただし、これらを回収すると、通常のGoでは生存していたはずのオブジェクトが回収され、finalizer（<code>runtime.SetFinalizer</code>で登録された後始末処理）が実行される可能性があります。これは副作用やpanicなど、本来観測されなかった挙動を生じさせます。そのためGolfでも、回収候補からfinalizer付きオブジェクトへ到達できる場合は、検出結果の報告にとどめて回収しない処理が設けられています。</p>
<p>一方、Proposal #74609を経てGoへ取り込まれた機能では、自動回復を採用していません。Proposalでは、論文の実装からの主な変更として、リークしたgoroutineとそのスタックからのみ到達可能なリソースを強制回収しないこと、<code>goroutineleak</code>プロファイルの取得を契機に検出用GCサイクルをオンデマンドで実行することが明示されています。なお、Go 1.27リリースノートには「Special thanks to Vlad Saioc at Uber for contributing this work.」と、この機能に貢献したSaioc氏への謝辞が記されています。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>観点</th>
<th>論文のGolf</th>
<th>Goに取り込まれた機能</th>
</tr>
</thead>
<tbody><tr>
<td>目的</td>
<td>検出と自動回復</td>
<td>検出と診断情報の提供</td>
</tr>
<tr>
<td>起動方法</td>
<td>拡張したGCで検出</td>
<td>プロファイル取得時にオンデマンド実行</td>
</tr>
<tr>
<td>検出後</td>
<td>対象goroutineの停止とメモリ回収を試みる</td>
<td>プロファイルへ報告し、GCでトレースする</td>
</tr>
<tr>
<td>スタック</td>
<td>回収対象になり得る</td>
<td>通常のGCと同様にトレースする</td>
</tr>
<tr>
<td>出力</td>
<td>Golf独自の報告</td>
<td><code>runtime/pprof</code>と<code>net/http/pprof</code></td>
</tr>
<tr>
<td>既存プログラムへの影響</td>
<td>回収で挙動が変わり得るため、finalizerを考慮した回復処理が必要</td>
<td>強制回収せず、既存の挙動を変えない</td>
</tr>
</tbody></table></div>
<p>Goへの取り込みで自動回復を見送ったのは、前述のとおり、強制回収がfinalizerの実行などを通じてプログラムの挙動を変えてしまうためです。そのためGoは、リークと判定したgoroutineもそれまでどおりGCの管理下に残したまま、プロファイルで報告するだけにとどめています。この機能が担うのはリークの検出と診断までであり、報告されたリークを修正するのは、そのコードを書いた私たち開発者自身です。</p>
<h2 id="実際に試す">実際に試す</h2><p>ここからは検証編です。意図的にgoroutineをリークさせる小さなプログラムを用意し、<code>goroutineleak</code>プロファイルでリークが検出される様子を確認します。</p>
<h3 id="再現コード">再現コード</h3><div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-2i3s02-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-2i3s02-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;log&quot;</span></span><br><span class="line">	<span class="string">&quot;net/http&quot;</span></span><br><span class="line">	_ <span class="string">&quot;net/http/pprof&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">startLeaks</span><span class="params">(n <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> <span class="keyword">range</span> n &#123;</span><br><span class="line">		ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span><br><span class="line"></span><br><span class="line">		<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">			&lt;-ch</span><br><span class="line">		&#125;()</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	startLeaks(<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">	fmt.Println(<span class="string">&quot;pprof: http://127.0.0.1:6060/debug/pprof/&quot;</span>)</span><br><span class="line">	log.Fatal(http.ListenAndServe(<span class="string">&quot;127.0.0.1:6060&quot;</span>, <span class="literal">nil</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p><code>ch</code>を受信するgoroutineは存在しますが、<code>ch</code>へ送信も<code>close</code>もしていません。ループを抜けた後は実行可能なコードのどこからも<code>ch</code>へ到達できず、3つのgoroutineは再開不能、つまりリークです。</p>
<p><code>net/http/pprof</code>のブランクインポートと、<code>ListenAndServe</code>の第2引数の<code>nil</code>により、pprofのハンドラーが登録された<code>http.DefaultServeMux</code>が使われます。</p>
<h3 id="ビルドと実行">ビルドと実行</h3><p>検証には、正式リリース前のGo 1.27 RC2を使用しました。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go1.27rc2 build -trimpath -o leakdemo .</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">./leakdemo</span></span><br><span class="line">pprof: http://127.0.0.1:6060/debug/pprof/</span><br></pre></td></tr></table></figure>

<p>実験フラグの指定は不要です。</p>
<p>プログラムを実行したまま、別のターミナルから<code>/debug/pprof/goroutineleak</code>エンドポイントにアクセスすると、リーク検出の結果を取得できます。出力形式は<code>debug</code>パラメーターで切り替わります。</p>
<h3 id="debug-1-件数と集約スタック">debug&#x3D;1: 件数と集約スタック</h3><div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-2i3s02-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-2i3s02-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">curl -s <span class="string">&#x27;http://127.0.0.1:6060/debug/pprof/goroutineleak?debug=1&#x27;</span></span><br></pre></td></tr></table></figure></div>

<div class="code-block"><figure class="highlight text"><input type="checkbox" id="code-wrap-2i3s02-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-2i3s02-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">goroutineleak profile: total 3</span><br><span class="line">3 @ 0x100da4428 0x100d366e0 0x100d36264 0x100f4f2a4 0x100dab454</span><br><span class="line">#       0x100f4f2a3     main.startLeaks.func1+0x23      example.com/leakdemo/main.go:15</span><br></pre></td></tr></table></figure></div>

<p><code>total 3</code>は検出されたリークgoroutineの合計です。行頭の<code>3</code>は同じスタックに集約されたgoroutine数、<code>@</code>以降は各スタックフレームのPC値（プログラムカウンタ）、<code>#</code>行が関数名とソース位置を示します。3つのgoroutineが同じ箇所（<code>main.go:15</code>の受信待ち）でリークしているとわかります。</p>
<h3 id="debug-2-完全なスタックダンプと-leaked">debug&#x3D;2: 完全なスタックダンプと(leaked)</h3><div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-2i3s02-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-2i3s02-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">curl -s <span class="string">&#x27;http://127.0.0.1:6060/debug/pprof/goroutineleak?debug=2&#x27;</span></span><br></pre></td></tr></table></figure></div>

<p><code>debug=2</code>では、リークしたgoroutineだけでなく、取得時点で存在するすべてのgoroutineの完全なスタックダンプが出力されます。以下はリーク部分の抜粋です。</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">goroutine 36 [chan receive (leaked)]:</span><br><span class="line">main.startLeaks.func1()</span><br><span class="line">        example.com/leakdemo/main.go:15 +0x24</span><br><span class="line">created by main.startLeaks in goroutine 1</span><br><span class="line">        example.com/leakdemo/main.go:14 +0x24</span><br></pre></td></tr></table></figure>

<p>状態表示の<code>chan receive</code>はチャネル受信待ちを、<code>(leaked)</code>はリーク判定を表します。<code>main.go:15</code>が実際に待機している位置、<code>created by</code>以下がこのgoroutineを生成した位置です。待機箇所と生成元の両方を特定できます。</p>
<h3 id="goroutineプロファイルとの違い">goroutineプロファイルとの違い</h3><p>pprofには従来から、取得時点に存在するすべてのgoroutineのスタックを一覧する<code>goroutine</code>プロファイルがあります。ただし、その出力から、待機中のgoroutineが「将来再開する正常な待機」なのか「解除されないリーク」なのかを判断するのは読み手の仕事です。<code>goroutineleak</code>は、この判断を到達可能性解析で肩代わりし、解除不能と判定されたgoroutineだけを報告します。両者の用途を整理すると次のとおりです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>プロファイル</th>
<th>用途</th>
</tr>
</thead>
<tbody><tr>
<td><code>goroutine</code></td>
<td>取得時点で存在するgoroutineの状態とスタックを確認する</td>
</tr>
<tr>
<td><code>goroutineleak</code></td>
<td>解除不能と判定されたgoroutineを確認する</td>
</tr>
</tbody></table></div>
<p>参考として、<code>goroutine</code>プロファイルは次のコマンドで取得できます。</p>
<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-2i3s02-5" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-2i3s02-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">curl -s <span class="string">&#x27;http://127.0.0.1:6060/debug/pprof/goroutine?debug=2&#x27;</span></span><br></pre></td></tr></table></figure></div>

<p>前述のとおり、<code>debug=2</code>では<code>goroutineleak</code>もリークだけでなく完全なスタックダンプを出力するため、出力の行数は<code>goroutine?debug=2</code>と同程度または同じになる場合があります。検証では、どちらも81行でした。ただし、取得処理自身のスタックや取得タイミングが異なるため、内容が完全に同一になるとは限りません。</p>
<h2 id="リークを修正する">リークを修正する</h2><p>修正の共通原則は次のとおりです。</p>
<blockquote>
<p>goroutineを待機させるなら、その待機を解除できるコードパスを用意する。</p>
</blockquote>
<p>解除経路を用意できないのであれば、待機そのものを無くします。この原則に沿って、再現コードを4つの方針で修正します。使い分けは次のとおりです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>方針</th>
<th>適する状況</th>
</tr>
</thead>
<tbody><tr>
<td>方針1: 待機を削除</td>
<td>同期が不要</td>
</tr>
<tr>
<td>方針2: <code>close</code>または送信</td>
<td>明確な完了通知がある</td>
</tr>
<tr>
<td>方針3: <code>context</code>でキャンセル</td>
<td>外部からキャンセルしたい</td>
</tr>
<tr>
<td>方針4: タイムアウト</td>
<td>相手の応答が保証されない</td>
</tr>
</tbody></table></div>
<h3 id="方針1-待機を削除">方針1: 待機を削除</h3><p>同期が不要なら、チャネルと受信待ちを削除します。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-2i3s02-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-2i3s02-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;log&quot;</span></span><br><span class="line">	<span class="string">&quot;net/http&quot;</span></span><br><span class="line">	_ <span class="string">&quot;net/http/pprof&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">startWorkers</span><span class="params">(n <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> <span class="keyword">range</span> n &#123;</span><br><span class="line">		<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">			<span class="comment">// チャネルでの待機を削除し、何らかの処理（例: doWork()）だけを実行する</span></span><br><span class="line">		&#125;()</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	startWorkers(<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">	fmt.Println(<span class="string">&quot;pprof: http://127.0.0.1:6060/debug/pprof/&quot;</span>)</span><br><span class="line">	log.Fatal(http.ListenAndServe(<span class="string">&quot;127.0.0.1:6060&quot;</span>, <span class="literal">nil</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h3 id="方針2-closeまたは送信">方針2: closeまたは送信</h3><p>完了通知が必要なら、チャネルを閉じる処理を用意します。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-2i3s02-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-2i3s02-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;log&quot;</span></span><br><span class="line">	<span class="string">&quot;net/http&quot;</span></span><br><span class="line">	_ <span class="string">&quot;net/http/pprof&quot;</span></span><br><span class="line">	<span class="string">&quot;sync&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">startWorkers</span><span class="params">(n <span class="type">int</span>)</span></span> *sync.WaitGroup &#123;</span><br><span class="line">	<span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line"></span><br><span class="line">	<span class="keyword">for</span> <span class="keyword">range</span> n &#123;</span><br><span class="line">		ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span><br><span class="line">		wg.Add(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">		<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">			<span class="keyword">defer</span> wg.Done()</span><br><span class="line">			&lt;-ch <span class="comment">// closeされると受信可能になり、待機が解除される</span></span><br><span class="line">		&#125;()</span><br><span class="line"></span><br><span class="line">		<span class="comment">// 待機と対になる解除役。実務では処理の完了時にcloseする</span></span><br><span class="line">		<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">			<span class="built_in">close</span>(ch)</span><br><span class="line">		&#125;()</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">return</span> &amp;wg</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	wg := startWorkers(<span class="number">3</span>)</span><br><span class="line">	wg.Wait() <span class="comment">// すべてのworkerの終了を待つ</span></span><br><span class="line"></span><br><span class="line">	fmt.Println(<span class="string">&quot;pprof: http://127.0.0.1:6060/debug/pprof/&quot;</span>)</span><br><span class="line">	log.Fatal(http.ListenAndServe(<span class="string">&quot;127.0.0.1:6060&quot;</span>, <span class="literal">nil</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>この例は、待機と解除を対にする構造を確認するための最小例です。実務では、実際の処理主体が完了時にチャネルを閉じます。1回限りの通知であれば、<code>close</code>の代わりにチャネルへの送信でも待機を解除できます。</p>
<h3 id="方針3-contextでキャンセル">方針3: contextでキャンセル</h3><p>外部から停止したい場合は<code>context.Context</code>を使用します。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-2i3s02-8" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-2i3s02-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;context&quot;</span></span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;log&quot;</span></span><br><span class="line">	<span class="string">&quot;net/http&quot;</span></span><br><span class="line">	_ <span class="string">&quot;net/http/pprof&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">startWorkers</span><span class="params">(ctx context.Context, n <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> <span class="keyword">range</span> n &#123;</span><br><span class="line">		<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">			&lt;-ctx.Done() <span class="comment">// cancel()が呼ばれると受信可能になる</span></span><br><span class="line">		&#125;()</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	ctx, cancel := context.WithCancel(context.Background())</span><br><span class="line"></span><br><span class="line">	startWorkers(ctx, <span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">	cancel() <span class="comment">// 必要なタイミングで確実に呼び出す</span></span><br><span class="line"></span><br><span class="line">	fmt.Println(<span class="string">&quot;pprof: http://127.0.0.1:6060/debug/pprof/&quot;</span>)</span><br><span class="line">	log.Fatal(http.ListenAndServe(<span class="string">&quot;127.0.0.1:6060&quot;</span>, <span class="literal">nil</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p><code>cancel()</code>によって<code>ctx.Done()</code>が受信可能になり、workerが終了します。キャンセル関数が存在するだけではリークは防げません。必要なタイミングで確実に呼び出す必要があります。</p>
<h3 id="方針4-タイムアウト">方針4: タイムアウト</h3><p>相手の応答が保証されない場合は、待機に期限を設けます。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-2i3s02-9" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-2i3s02-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;log&quot;</span></span><br><span class="line">	<span class="string">&quot;net/http&quot;</span></span><br><span class="line">	_ <span class="string">&quot;net/http/pprof&quot;</span></span><br><span class="line">	<span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">startWorkers</span><span class="params">(n <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> <span class="keyword">range</span> n &#123;</span><br><span class="line">		ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="keyword">struct</span>&#123;&#125;)</span><br><span class="line"></span><br><span class="line">		<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">			<span class="keyword">select</span> &#123;</span><br><span class="line">			<span class="keyword">case</span> &lt;-ch: <span class="comment">// 通常の完了通知</span></span><br><span class="line">			<span class="keyword">case</span> &lt;-time.After(<span class="number">2</span> * time.Second): <span class="comment">// 期限が来たら待機を解除する</span></span><br><span class="line">			&#125;</span><br><span class="line">		&#125;()</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	startWorkers(<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">	fmt.Println(<span class="string">&quot;pprof: http://127.0.0.1:6060/debug/pprof/&quot;</span>)</span><br><span class="line">	log.Fatal(http.ListenAndServe(<span class="string">&quot;127.0.0.1:6060&quot;</span>, <span class="literal">nil</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p><code>time.After</code>は<code>ch</code>とは別のチャネルを返します。<code>select</code>は、<code>ch</code>またはタイマー用チャネルのうち、先に受信可能になったcaseを1つ選びます（複数のcaseが同時に受信可能な場合は、ランダムに1つ選ばれます）。実務では<code>context.WithTimeout</code>を使い、キャンセルとタイムアウトをまとめて扱う方法もあります。</p>
<p>いずれの方針でも、修正後に<code>goroutineleak?debug=1</code>を再取得すると、リークが検出されなくなったことを確認できます。</p>
<h2 id="まとめ">まとめ</h2><ul>
<li>Go 1.27では、<code>goroutineleak</code>プロファイルを実験フラグなしで利用できます（<code>GOEXPERIMENT=goroutineleakprofile</code>は削除されました）</li>
<li>論文の研究実装Golfが目指した自動回復は採用されておらず、<code>goroutineleak</code>は修正すべきコードを発見するための診断機能です</li>
<li><code>debug=1</code>は件数と集約スタックの確認に向きます</li>
<li><code>debug=2</code>は完全なgoroutineスタックダンプを出力し、リークには<code>(leaked)</code>が付きます</li>
<li>到達可能性に基づく検出のため偽陰性はあり得ます。<code>total 0</code>はリークが存在しないことの証明にはなりません</li>
<li>修正の基本は、不要な待機を削除するか、待機を解除する経路を用意することです</li>
</ul>
<h2 id="参考">参考</h2><ul>
<li>Go 1.27リリース連載（インデックス）</li>
<li>Go 1.26 リリース連載 Goroutine Leak Profiles</li>
<li>Go 1.26の新GC「Green Tea（緑茶）」解説</li>
<li>Go 1.27 Release Notes</li>
<li>Go 1.26 Release Notes</li>
<li>proposal: runtime&#x2F;pprof,runtime: new goroutine leak profile · golang&#x2F;go#74609</li>
<li>Design Document: Goroutine leak detection via garbage collection</li>
<li>Saioc et al., Dynamic Partial Deadlock Detection and Recovery via Garbage Collection (ASPLOS 2025)</li>
</ul>
]]></content>
    <summary type="html">goroutineリーク検出プロファイル goroutineleak が、Go 1.27で正式機能になりました。正式化後の使い方と、検出したリークをどう修正するかを中心に紹介します。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.27" scheme="https://future-architect.github.io/tags/Go1-27/"/>
  </entry>
  <entry>
    <title>Go1.27リリース連載：encoding/json/v2</title>
    <link href="https://future-architect.github.io/articles/20260731a/"/>
    <id>https://future-architect.github.io/articles/20260731a/</id>
    <published>2026-07-30T15:00:00.000Z</published>
    <updated>2026-07-30T15:00:00.000Z</updated>
    <author><name>辻大志郎</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260731a/top.jpg" alt="" width="512" height="286">

<p>The Go gopher was designed by Renee French.</p>
<p>Go 1.27 ブログ連載 の 4 本目です。</p>
<h2 id="はじめに">はじめに</h2><p>製造エネルギーサービス事業部の辻です。</p>
<p>この記事では、Go1.27で新たに利用可能になる <code>encoding/json/v2</code> を取り上げます。</p>
<p><code>encoding/json/v2</code> については、昨年のGo1.25連載で後藤さんが Go 1.25リリース連載 encoding&#x2F;json&#x2F;v2（experimental） で詳しく取り上げています。当時は <code>GOEXPERIMENT=jsonv2</code> を有効にして試す実験的機能という位置づけで、<code>omitempty</code> の挙動変化と性能に焦点が当てられていました。本記事では、その1年でどこが変わったかを中心に見ていきます。</p>
<h2 id="Go1-27での変更点サマリ">Go1.27での変更点サマリ</h2><ul>
<li><code>encoding/json/v2</code> と <code>encoding/json/jsontext</code> が標準ライブラリ入りする（<code>GOEXPERIMENT</code> 不要）</li>
<li>既存の <code>encoding/json</code>（v1）の内部実装が、v2エンジンをバックエンドとして全面的に書き換えられた</li>
<li><code>GOEXPERIMENT</code> の意味が反転した<ul>
<li>Go1.25&#x2F;1.26：<code>GOEXPERIMENT=jsonv2</code> で有効化</li>
<li>Go1.27：デフォルト有効。旧実装に戻したいときのみ <code>GOEXPERIMENT=nojsonv2</code>（将来削除予定）</li>
</ul>
</li>
<li>experimentalで使えた <code>format</code> タグが標準から外れた</li>
<li><code>encoding/json/v2</code> はv1よりも厳格で相互運用性の高いデフォルト挙動を採用した</li>
</ul>
<p><code>encoding/json</code>パッケージ自体は非推奨化・削除されません。従来のAPI（<code>json.Marshal</code>や<code>json.Unmarshal</code>など）はそのまま使え、Go1.27へ上げるだけで内部的にv2エンジンに切り替わります。</p>
<h2 id="深堀り1：encoding-jsonの内部実装がv2ベースに置き換わり、GOEXPERIMENTの意味合いが反転した">深堀り1：<code>encoding/json</code>の内部実装がv2ベースに置き換わり、GOEXPERIMENTの意味合いが反転した</h2><p>Go1.25時点は「<code>GOEXPERIMENT=jsonv2</code> を付けると内部実装が置き換わる」というオプトインでした。Go1.27ではこれが逆転し、何もしなくても <code>encoding/json</code> の内部がv2エンジンで動きます。<code>encoding/json</code>パッケージには、旧実装（<code>decode.go</code>等）とv2ベースの新実装（<code>v2_decode.go</code>等）が両方コミットされており、ビルドタグで排他的に切り替わります。挙動が怪しいときは <code>GOEXPERIMENT=nojsonv2</code> で旧実装に戻し、切り分けられます。</p>
<p>1つ注意点があります。リリースノートには「Marshaling and unmarshaling behavior is preserved, but the exact text of error messages may differ.」とあり、挙動自体は互換が保たれますが、エラーメッセージの文言（<code>err.Error()</code>の文字列）は変わり得るとされています。<code>err.Error() == &quot;...&quot;</code>のような文字列完全一致でテストしているコードは、Go1.27で壊れる可能性があります。エラーは文字列ではなく <code>errors.Is</code> &#x2F; <code>errors.As</code> で判定するのが、よいプラクティスです。</p>
<h2 id="深堀り2：format-タグが標準から外れた">深堀り2：<code>format</code> タグが標準から外れた</h2><p>Go1.25 &#x2F; 1.26のexperimentalでは、フィールドのエンコード表現を構造体タグで指定する <code>format</code> オプションが使えました<sup id="fnref:1">1</sup>。例えば <code>time.Time</code> は本来RFC 3339形式の文字列でエンコードされますが、<code>format:RFC3339</code> と明示的に書くこともできましたし、<code>Format</code>メソッドが解釈できる任意のレイアウト文字列（例: <code>format:&quot;2006-01-02&quot;</code>）を指定して表現を変えることもできました。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1v6t5uw-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1v6t5uw-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> Event <span class="keyword">struct</span> &#123;</span><br><span class="line">	At time.Time <span class="string">`json:&quot;at,format:RFC3339&quot;`</span> <span class="comment">// &quot;2026-07-30T00:00:00Z&quot; のような表現</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>この <code>format</code> タグはGo1.27の標準APIからは外れます（#79071）。Go言語自体にtyped struct tagsを導入する提案（#74472）を見越し、パッケージ内に専用のDSLを持たせない方針への転換です。</p>
<h2 id="深堀り3：デフォルト挙動の変化">深堀り3：デフォルト挙動の変化</h2><p><code>encoding/json/v2</code> APIのデフォルト挙動はv1から複数変わっています。代表的な差分です。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">挙動項目</th>
<th align="left">v1</th>
<th align="left">v2</th>
<th align="left">v1に戻すオプション</th>
</tr>
</thead>
<tbody><tr>
<td align="left">不正なUTF-8</td>
<td align="left"><code>U+FFFD</code>(�) に置換</td>
<td align="left">エラー</td>
<td align="left"><code>jsontext.AllowInvalidUTF8(true)</code></td>
</tr>
<tr>
<td align="left">重複キー</td>
<td align="left">許容（後の値が前の値を置換またはマージ）</td>
<td align="left">エラー</td>
<td align="left"><code>jsontext.AllowDuplicateNames(true)</code></td>
</tr>
<tr>
<td align="left">フィールド名マッチ</td>
<td align="left">大文字小文字を無視</td>
<td align="left">完全一致のみ</td>
<td align="left"><code>MatchCaseInsensitiveNames(true)</code></td>
</tr>
<tr>
<td align="left">nilスライス&#x2F;マップ</td>
<td align="left"><code>null</code></td>
<td align="left">通常は <code>[]</code> &#x2F; <code>&#123;&#125;</code></td>
<td align="left"><code>FormatNilSliceAsNull(true)</code> &#x2F; <code>FormatNilMapAsNull(true)</code></td>
</tr>
<tr>
<td align="left"><code>time.Duration</code></td>
<td align="left">ナノ秒の数値</td>
<td align="left">エラー</td>
<td align="left"><code>FormatDurationAsNano(true)</code></td>
</tr>
</tbody></table></div>
<p>nilスライス&#x2F;マップが <code>null</code> ではなく <code>[]</code> &#x2F; <code>&#123;&#125;</code> としてエンコードされるのは、個人的には嬉しいポイントです。v1では、nilスライスと空スライスでエンコード結果が異なり（<code>var a []string</code> は <code>null</code>、<code>b := []string&#123;&#125;</code> は <code>[]</code>）、レスポンスを常に <code>[]</code> にしたいなら明示的に空スライスで初期化する必要がありました。v2ではこの初期化漏れそのものが不要になります。厳密にはREST APIのレスポンス互換に影響があるため、後方互換性のために <code>FormatNilSliceAsNull()</code> &#x2F; <code>FormatNilMapAsNull()</code> のオプションが用意されています。</p>
<p><code>time.Duration</code> はv1では内部表現のナノ秒整数がそのままJSON数値になっていました（<code>5 * time.Second</code> は <code>5000000000</code>）。この表現を続けるべきか文字列（<code>&quot;5s&quot;</code>）にすべきかがissue上で決着しておらず（#71631）、v2では意図的にデフォルト表現が定められていません。そのため何も指定しないとMarshal、Unmarshalともにエラーになります。v1互換のナノ秒整数のままでよい場合は<code>encoding/json.FormatDurationAsNano(true)</code>を指定し、文字列など別の表現にしたい場合は独自型 + <code>MarshalerTo</code> &#x2F; <code>UnmarshalerFrom</code> で明示的に定義します。</p>
<h2 id="まとめ">まとめ</h2><ul>
<li>experimentalからの1年で、<code>GOEXPERIMENT</code> が反転し、v1がv2ベースに再実装された</li>
<li>Go1.27へ上げるだけで、既存コードのまま内部的にv2に切り替わる</li>
<li>experimentalで使えた <code>format</code> タグは標準から外れる</li>
<li>フィールド名マッチの厳格化、nilスライス&#x2F;マップ、<code>time.Duration</code> は特に気をつける必要あり</li>
</ul>
<div id="footnotes"><hr><div id="footnotelist"><ol style="list-style:none; padding-left: 0;"><li id="fn:1"><span style="vertical-align: top; padding-right: 10px;">1.</span><span style="vertical-align: top;">A new experimental Go API for JSON</span> ↩</li></ol></div></div>]]></content>
    <summary type="html">Go1.27で新たに利用可能になる encoding/json/v2 を取り上げます。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.27" scheme="https://future-architect.github.io/tags/Go1-27/"/>
    <category term="JSON" scheme="https://future-architect.github.io/tags/JSON/"/>
    <category term="encoding/json" scheme="https://future-architect.github.io/tags/encoding-json/"/>
  </entry>
  <entry>
    <title>Go 1.27 リリース連載：ジェネリクスメソッド (generic methods)</title>
    <link href="https://future-architect.github.io/articles/20260730a/"/>
    <id>https://future-architect.github.io/articles/20260730a/</id>
    <published>2026-07-29T15:00:00.000Z</published>
    <updated>2026-07-29T15:00:00.000Z</updated>
    <author><name>市川裕也</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260730a/top.jpg" alt="" width="512" height="286">

<p>The Go gopher was designed by Renee French.</p>
<p>Go 1.27 ブログ連載 の 3 本目です。</p>
<h2 id="はじめに">はじめに</h2><p>こんにちは。 CSIG (Cyber Security Innovation Group) の市川です。普段は、 FutureVuls の開発・運用に従事しており、最近はパフォーマンスの課題に主に取り組んでいます。</p>
<p>本記事では、 Go 1.27 より導入される「ジェネリックメソッド (generic methods)」について紹介します。</p>
<p>なお、本記事は go1.27rc1 時点の動作に基づいています。</p>
<h2 id="ジェネリックメソッドの概要">ジェネリックメソッドの概要</h2><p>以下の issue に、ジェネリックメソッドの Proposal がまとまっています。</p>
<ul>
<li>https://github.com/golang/go/issues/77273</li>
</ul>
<h3 id="できるようになったこと">できるようになったこと</h3><p>Go 1.26 までは、型パラメータを宣言できるのは、トップレベルの関数と型のみでした。</p>
<p>メソッドはレシーバの型パラメータ (下の例では <code>Stack[T]</code> の <code>T</code>) を利用できるだけで、メソッド自身が新しい型パラメータを宣言できず、次のように書くと文法エラーになる仕様でした。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1jvksh6-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1jvksh6-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// Go 1.26 以前は syntax error: method must have no type parameters</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Stack[T])</span></span> Map[U any](f <span class="function"><span class="keyword">func</span><span class="params">(T)</span></span> U) *Stack[U] &#123; ... &#125;</span><br><span class="line"><span class="comment">//                     ^^^^^^^ メソッド独自の型パラメータ U</span></span><br></pre></td></tr></table></figure></div>

<p>Go 1.27 ではこの制限が外れ、メソッドに対しても、型パラメータを宣言できるようになりました。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1jvksh6-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1jvksh6-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> Stack[T any] <span class="keyword">struct</span>&#123; items []T &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Stack[T])</span></span> Map[U any](f <span class="function"><span class="keyword">func</span><span class="params">(T)</span></span> U) *Stack[U] &#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h3 id="引き続きできないこと">引き続きできないこと</h3><p>型パラメータを持つメソッドを、インターフェースのメソッドとして宣言できません。<br>例えば、以下のような書き方は NG です。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> Mapper[T any] <span class="keyword">interface</span> &#123;</span><br><span class="line">    Map[U any](f <span class="function"><span class="keyword">func</span><span class="params">(T)</span></span> U) []U</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>また、ジェネリックメソッドはインターフェースのメソッドの実装としては扱われません。<br>例えば、以下の例では、 <code>S</code> が <code>Map</code> メソッドを持っていても、<code>I</code> を満たしません。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1jvksh6-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1jvksh6-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> I <span class="keyword">interface</span> &#123;</span><br><span class="line">    Map(f <span class="function"><span class="keyword">func</span><span class="params">(<span class="type">int</span>)</span></span> <span class="type">int</span>) []<span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> S <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(S)</span></span> Map[U any](f <span class="function"><span class="keyword">func</span><span class="params">(<span class="type">int</span>)</span></span> U) []U &#123; <span class="keyword">return</span> <span class="literal">nil</span> &#125; <span class="comment">// ジェネリックメソッドである S.Map は、I.Map の実装としては扱われない</span></span><br><span class="line"><span class="keyword">var</span> _ I = S&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;&#125;</span><br></pre></td></tr></table></figure></div>

<p>コンパイルしようとすると、以下のエラーが出ます。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1jvksh6-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1jvksh6-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">main.go:9:11: cannot use S&#123;&#125; (value of struct <span class="built_in">type</span> S) as I value <span class="keyword">in</span> variable declaration: S does not implement I (wrong <span class="built_in">type</span> <span class="keyword">for</span> method Map)</span><br><span class="line">                have Map[U any](func(int) U) []U</span><br><span class="line">                want Map(func(int) int) []int</span><br></pre></td></tr></table></figure></div>

<h2 id="実装までの経緯">実装までの経緯</h2><h3 id="いつから提案されていたか">いつから提案されていたか</h3><p>ジェネリックメソッドについては、2021 年の時点で強い需要がありました。<br>以下は、中心となった提案です (執筆時点で 900 を超える 👍 が付いています)。</p>
<ul>
<li>https://github.com/golang/go/issues/49085</li>
</ul>
<p>以下のように、「呼び出しごとに変換後の型を変えたい」という要望でした。<br>構造体側に不要な型パラメータを生やすことなくこの要望を満たすには、ジェネリックメソッドが必要でした。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1jvksh6-5" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1jvksh6-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// stream[IN] を stream[OUT] へ変換したい。OUT は呼び出しごとに変わる</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(si *stream[IN])</span></span> Map[OUT any](f <span class="function"><span class="keyword">func</span><span class="params">(IN)</span></span> OUT) stream[OUT]</span><br></pre></td></tr></table></figure></div>

<blockquote>
<p>“This limitation prevents to define functional-like stream processing primitives … Allowing type parameters in methods would allow constructing DSLs that would greatly simplify some existing use cases.”<br>— mariomac, #49085 本文</p>
</blockquote>
<p>また、Go 1.23 で <code>iter.Seq[T]</code> が導入されてからは、「<code>seq.Map().Filter().Reduce()</code> のようにイテレータ操作をメソッドチェーンで書きたい」という需要も生まれました。この需要は、Proposal (#77273) の議論の中で、次のように言語化されています。</p>
<blockquote>
<p>“What people do want is to have methods like <code>Map</code>&#x2F;<code>Filter</code>&#x2F;<code>Reduce</code> on <code>iter.Seq[T]</code>, to allow chaining those calls and reading them left-to-right.”<br>— Merovius, #77273 (comment) (2026-02-15)</p>
</blockquote>
<h3 id="なぜジェネリクス導入時には見送られたのか">なぜジェネリクス導入時には見送られたのか</h3><p>インターフェースにジェネリクスメソッドを定義することには、設計当初から根本的な実装上の壁があることが知られていました。</p>
<p>ジェネリックメソッドを提案するにあたり、この壁を乗り越える必要があり、次の 3 つの実装案が検討されましたが、いずれも却下されました。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>実装案</th>
<th>内容</th>
<th>却下理由</th>
</tr>
</thead>
<tbody><tr>
<td>① インターフェース内のジェネリックメソッドを許可し、メソッドをリンク時にインスタンス化する</td>
<td>呼び出しグラフ全体を辿り、起こりうる全ての型引数を事前生成する</td>
<td>リンク時に呼び出しグラフ全体を見ても、reflection 経由の動的なメソッド呼び出しがある限り、必要な型引数の集合を確定できず、実現不可能。</td>
</tr>
<tr>
<td>② インターフェース内のジェネリックメソッドを許可し、メソッドを実行時にインスタンス化する</td>
<td>呼び出し時に必要な実体を生成する</td>
<td>JIT 相当が必要。実装が非常に複雑で、実行時に驚くほど遅くなってしまう。Go の「ソースを事前にすべて機械語に変換する」という方式 (AOT) に反する。</td>
</tr>
<tr>
<td>③ インターフェースにジェネリックメソッドを生やすのを諦める</td>
<td>ジェネリックメソッドはインターフェースを実装しない、という制約を加える</td>
<td>それなら、メソッドである意味が薄い</td>
</tr>
</tbody></table></div>
<p>① は、実現不可能でした。(詳細は、以下の補足を参照してください)</p>
<p>また、 ② も実装が非常に困難である上に、Go の「ソースを事前にすべて機械語に変換する」という方式にも反しており、非現実的でした。</p>
<p>残る案 ③ ですが、当初、Go では、「メソッド &#x3D; インターフェースを実装するもの」という考えが主流であり、「メソッドの存在意義の大部分はインターフェースの実装にあるので、インターフェースを満たさないならメソッドにする意味が不明確(トップレベル関数で十分)」と考えられていました。<br>そのため、③ 案も非現実的でした。</p>
<div class="note-container note-info note-has-title"><div class="note-title"><span class="note-icon"></span>補足: なぜ Go がインターフェースのメソッドに型パラメータを許していないのか</div><div class="note-body">

<p>Go ではコンパイル時にすべての型を決定する必要があります。<br>そのため、ジェネリックメソッドをインターフェースに定義できるようにすると、その呼び出しで必要になりうる全ての組み合わせ (&#x3D; 無限通りの組み合わせ) のコードをコンパイル時に用意する必要があり、これは実質実装不可能です。</p>
<p>そのため、Go ではインターフェースのメソッドに型パラメータを許していません。 (こちらは現在も当てはまります)</p>
<p>実例を用いて、この困難さを説明します。<br>こちらで紹介する思考実験については、Type Parameters Proposal#No parameterized method を参照してください。<br>また、https://github.com/golang/go/issues/49085#issuecomment-2316352221 でも詳しく説明されているため、合わせて参照してください。</p>
<hr>
<p>以下の <code>HasIdentity</code> のように、インターフェースにジェネリックメソッドを定義できると仮定します。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1jvksh6-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1jvksh6-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> p2</span><br><span class="line"><span class="comment">// HasIdentity is an interface that matches any type with a</span></span><br><span class="line"><span class="comment">// parameterized Identity method.</span></span><br><span class="line"><span class="keyword">type</span> HasIdentity <span class="keyword">interface</span> &#123;</span><br><span class="line">    Identity[T any](T) T</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>このインターフェースのジェネリックメソッドがどのような問題を引き起こすかを理解するため、さらに以下のようなパッケージ群を考えます。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> p1</span><br><span class="line"><span class="keyword">type</span> S <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(S)</span></span> Identity[T any](v T) T &#123; <span class="keyword">return</span> v &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> p3</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CheckIdentity</span><span class="params">(v <span class="keyword">interface</span>&#123;&#125;)</span></span> &#123;</span><br><span class="line">    <span class="keyword">if</span> vi, ok := v.(p2.HasIdentity); ok &#123;</span><br><span class="line">        <span class="keyword">if</span> got := vi.Identity[<span class="type">int</span>](<span class="number">0</span>); got != <span class="number">0</span> &#123;</span><br><span class="line">            <span class="built_in">panic</span>(got)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> p4</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CheckSIdentity</span><span class="params">()</span></span> &#123;</span><br><span class="line">    p3.CheckIdentity(p1.S&#123;&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Go では、実際に使われる型引数の組み合わせごとに、インスタンス化されたコード (この例では <code>S.Identity[int]</code> の実体) がコンパイル時に生成される必要があります。<br>しかし、上記のようなパッケージ群をコンパイルする際、 <code>S.Identity[int]</code> はどのパッケージも生成できません。</p>
<ul>
<li>p1 をコンパイルするとき: 「誰が <code>int</code> で呼ぶか」が分からないため、 <code>Identity[int]</code> を作れない</li>
<li>p3 をコンパイルするとき: p3 は <code>p1.S</code> の存在を知らないため、 <code>S.Identity[int]</code> を作れない</li>
<li>p4 をコンパイルするとき: p4 からは p3.CheckIdentity の関数本体が見えないため、p4 自身は Identity の型パラメータに int が渡されることを知らず、 <code>S.Identity[int]</code> を作れない</li>
</ul>
<p>このため、「コンパイル時に、<code>HasIdentity</code> が実際に取り得る型だけ作る」方法を取ることができません。<br>この状況下で、コンパイル時にすべての型を決定するためには、 <code>S.Identity</code> の実体を全部生成しておく必要があります。(<code>S.Identity[int]</code>, <code>S.Identity[string]</code>, <code>S.Identity[Hoge]</code>, etc…)<br>これは無限個の実体を事前に生成しなければいけないことを意味しており、実質不可能です。</p>
<p>このように、「ソースを事前にすべて機械語に変換する」という方式のもとでコンパイル不可能になるのを避けるため、インターフェースにジェネリックメソッドを定義できないようになっているのです。</p>
</div></div>

<h3 id="方針転換">方針転換</h3><p>2026 年 1 月に、Go の設計者の一人である Robert Griesemer が、③ の「インターフェースを諦める」案を再度提案しました。<br>これは、以下の結論に至ったことに起因します。</p>
<blockquote>
<p>具象メソッドはインターフェースとは関係なく、それ自体で有用な言語機能である</p>
</blockquote>
<p>この提案は無事受け入れられ、「インターフェースのジェネリックメソッド」は実装せず、「ジェネリックな具象メソッド」のみを実装するという方針で Go 1.27 に導入されることとなりました。</p>
<h2 id="何が嬉しいか">何が嬉しいか</h2><p>個人的に一番嬉しいと感じるのは、<strong>メソッドチェーンで複数の処理を左から右へ素直に書ける</strong>ことです。GoはJavaのStream APIの夢を見れるか？(見なくてもよい) で言及された、夢が見られそうです。</p>
<p><code>Stack[T]</code> に <code>Map</code> と <code>Filter</code> を持たせる場面で考えます。</p>
<p><code>Filter</code> のように型パラメータを増やさない操作は、Go 1.26 までもメソッドとして書けました。一方 <code>Map</code> には「変換後の型」を表す新しい型パラメータ <code>U</code> が必要です。メソッドは新しい型パラメータを宣言できなかったため、<code>Map</code> は <code>Stack[T]</code> を第一引数に取るパッケージ関数にするしかありませんでした。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1jvksh6-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1jvksh6-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// Go 1.26: Filter はメソッドにできるが、</span></span><br><span class="line"><span class="comment">// 新しい型パラメータ U が必要な Map はパッケージ関数にするしかない</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Map</span>[<span class="title">T</span>, <span class="title">U</span> <span class="title">any</span>]<span class="params">(s *Stack[T], f <span class="keyword">func</span>(T)</span></span> U) *Stack[U] &#123; ... &#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Stack[T])</span></span> Filter(pred <span class="function"><span class="keyword">func</span><span class="params">(T)</span></span> <span class="type">bool</span>) *Stack[T] &#123; ... &#125;</span><br></pre></td></tr></table></figure></div>

<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1jvksh6-8" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1jvksh6-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// Go 1.27: どちらもメソッドとして書ける</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Stack[T])</span></span> Map[U any](f <span class="function"><span class="keyword">func</span><span class="params">(T)</span></span> U) *Stack[U] &#123; ... &#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Stack[T])</span></span> Filter(pred <span class="function"><span class="keyword">func</span><span class="params">(T)</span></span> <span class="type">bool</span>) *Stack[T] &#123; ... &#125;</span><br></pre></td></tr></table></figure></div>

<p>単発で呼ぶだけであれば、<code>Map(stack, f)</code> が <code>stack.Map(f)</code> になる程度の違いのため、後者の方が自然な書き方とはいえ、そこまで大きな差ではありません。</p>
<p>効いてくるのは、処理を続けて適用したい場合です。</p>
<p><code>Map</code> ⇒ <code>Filter</code> ⇒ <code>Map</code> と処理を続けたい場合、Go 1.26 までは、<code>Map</code> が現れるたびにメソッドチェーンが関数呼び出しで分断され、以下のように入れ子になります。実行される順番 (内側の <code>Map</code> → <code>Filter</code> → 外側の <code>Map</code>) と読む順番が一致せず、続けて適用する処理が増えるほど、可読性が下がってしまいます。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1jvksh6-9" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1jvksh6-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">words := &amp;Stack[<span class="type">string</span>]&#123;items: []<span class="type">string</span>&#123;<span class="string">&quot;go&quot;</span>, <span class="string">&quot;generics&quot;</span>, <span class="string">&quot;methods&quot;</span>&#125;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Map の部分だけ関数呼び出しになり、チェーンが分断される</span></span><br><span class="line">result := Map(</span><br><span class="line">    Map(words, <span class="function"><span class="keyword">func</span><span class="params">(s <span class="type">string</span>)</span></span> <span class="type">int</span> &#123; <span class="keyword">return</span> <span class="built_in">len</span>(s) &#125;).</span><br><span class="line">        Filter(<span class="function"><span class="keyword">func</span><span class="params">(n <span class="type">int</span>)</span></span> <span class="type">bool</span> &#123; <span class="keyword">return</span> n &gt; <span class="number">2</span> &#125;),</span><br><span class="line">    <span class="function"><span class="keyword">func</span><span class="params">(n <span class="type">int</span>)</span></span> <span class="type">string</span> &#123; <span class="keyword">return</span> fmt.Sprint(n) &#125;,</span><br><span class="line">)</span><br></pre></td></tr></table></figure></div>

<p>Go 1.27 以降は、以下のように処理の順番どおりに、左から右へ素直に読めるメソッドチェーンの書き方が可能になります。<br>Java の Stream API と同じような書き心地が得られます。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1jvksh6-10" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1jvksh6-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">words := &amp;Stack[<span class="type">string</span>]&#123;items: []<span class="type">string</span>&#123;<span class="string">&quot;go&quot;</span>, <span class="string">&quot;generics&quot;</span>, <span class="string">&quot;methods&quot;</span>&#125;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 型を変えながら、処理の順番どおりに読めるメソッドチェーン</span></span><br><span class="line">result := words.</span><br><span class="line">    Map(<span class="function"><span class="keyword">func</span><span class="params">(s <span class="type">string</span>)</span></span> <span class="type">int</span> &#123; <span class="keyword">return</span> <span class="built_in">len</span>(s) &#125;).</span><br><span class="line">    Filter(<span class="function"><span class="keyword">func</span><span class="params">(n <span class="type">int</span>)</span></span> <span class="type">bool</span> &#123; <span class="keyword">return</span> n &gt; <span class="number">2</span> &#125;).</span><br><span class="line">    Map(<span class="function"><span class="keyword">func</span><span class="params">(n <span class="type">int</span>)</span></span> <span class="type">string</span> &#123; <span class="keyword">return</span> fmt.Sprint(n) &#125;)</span><br></pre></td></tr></table></figure></div>

<h2 id="まとめ">まとめ</h2><p>本記事では、ジェネリックメソッドを紹介しました。</p>
<p>ジェネリックメソッドの導入によってメソッドチェーンが書きやすくなり、今後、様々なライブラリでより便利なメソッドが提供されていくのではないでしょうか。</p>
<p>今後の Go の発展が非常に楽しみになる機能だと感じました。</p>
<p>明日は、辻さんのjson v2の紹介です。</p>
]]></content>
    <summary type="html">Go 1.27 より導入されるジェネリックメソッド (generic methods)について紹介します。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.27" scheme="https://future-architect.github.io/tags/Go1-27/"/>
    <category term="ジェネリクス" scheme="https://future-architect.github.io/tags/%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%82%AF%E3%82%B9/"/>
  </entry>
  <entry>
    <title>Go 1.27のgo mod tidyの更新点</title>
    <link href="https://future-architect.github.io/articles/20260729a/"/>
    <id>https://future-architect.github.io/articles/20260729a/</id>
    <published>2026-07-28T15:00:00.000Z</published>
    <updated>2026-07-28T15:00:00.000Z</updated>
    <author><name>真野隼記</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260729a/top.jpg" alt="" width="512" height="279">

<p>The Go gopher was designed by Renee French.</p>
<h2 id="はじめに">はじめに</h2><p>TIG真野です。Go 1.27 リリース連載の2本目です。</p>
<p>Go 1.27で <code>go mod tidy</code> に追加された <code>require</code> ブロックの自動マージについて紹介します。リリースノート上は数行のさらっとした変更ですが、2022年起票のIssueから約4年の経緯があります。</p>
<p>検証には <code>go1.27rc2 linux/amd64</code> を利用しました。</p>
<h2 id="アップデート内容">アップデート内容</h2><p>Go 1.27の <code>go mod tidy</code> は、<code>go.mod</code> 内に散らばった <code>require</code> ブロックを <strong>最大2つ（直接依存用＋間接依存用）に自動マージ</strong> するようになります。</p>
<ul>
<li><code>go.mod</code> の <code>go</code> ディレクティブが <code>1.27</code> 以上の場合のみ作動します</li>
<li>ブロックに付いたコメントは保持されます。直接・間接が混在するブロックのコメントは、マージ後の直接依存ブロック側に付きます</li>
<li>提案Issueは cmd&#x2F;go: mod tidy should join “require” sections if there are more than two #56471、実装は CL 738740 です</li>
</ul>
<p>いきなりですが、Before&#x2F;Afterの差分がこちらです。</p>
<p>3つに増殖してしまった <code>require</code> ブロックが、<code>go</code> ディレクティブを <code>1.27</code> に上げて <code>go mod tidy</code> するだけで、綺麗な2ブロックに整頓されます（2つ目のブロックが消えました）。</p>
<div class="code-block"><figure class="highlight diff_gomod"><input type="checkbox" id="code-wrap-h92n71-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-h92n71-1" title="コードの折り返しを切り替える"></label><figcaption><span>go.mod</span></figcaption><table><tbody><tr><td class="code"><pre><span class="line deletion">--- a/<span class="keyword-plain">go</span>.mod</span><br><span class="line addition">+++ b/<span class="keyword-plain">go</span>.mod</span><br><span class="line">@@ -1,23 +1,20 @@</span><br><span class="line"> <span class="keyword-plain">module</span> <span>example.com/tidydemo</span></span><br><span class="line"></span><br><span class="line deletion">-<span class="keyword-plain">go</span> <span>1.26.5</span></span><br><span class="line addition">+<span class="keyword-plain">go</span> <span>1.27</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword-plain">require</span> (</span><br><span class="line"> 	<span>github.com/fatih/color</span> <span>v1.19.0</span></span><br><span class="line addition">+	<span>github.com/spf13/cobra</span> <span>v1.8.1</span></span><br><span class="line"> 	<span>github.com/stretchr/testify</span> <span>v1.11.1</span></span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line deletion">-<span class="keyword-plain">require</span> (</span><br><span class="line deletion">-	<span>github.com/inconshreveable/mousetrap</span> <span>v1.1.0</span> <span class="comment">// indirect</span></span><br><span class="line deletion">-	<span>github.com/spf13/pflag</span> <span>v1.0.5</span> <span class="comment">// indirect</span></span><br><span class="line deletion">-)</span><br><span class="line deletion">-</span><br><span class="line"> <span class="keyword-plain">require</span> (</span><br><span class="line"> 	<span>github.com/davecgh/go-spew</span> <span>v1.1.1</span> <span class="comment">// indirect</span></span><br><span class="line addition">+	<span>github.com/inconshreveable/mousetrap</span> <span>v1.1.0</span> <span class="comment">// indirect</span></span><br><span class="line"> 	<span>github.com/mattn/go-colorable</span> <span>v0.1.14</span> <span class="comment">// indirect</span></span><br><span class="line"> 	<span>github.com/mattn/go-isatty</span> <span>v0.0.20</span> <span class="comment">// indirect</span></span><br><span class="line"> 	<span>github.com/pmezard/go-difflib</span> <span>v1.0.0</span> <span class="comment">// indirect</span></span><br><span class="line deletion">-	<span>github.com/spf13/cobra</span> <span>v1.8.1</span></span><br><span class="line addition">+	<span>github.com/spf13/pflag</span> <span>v1.0.5</span> <span class="comment">// indirect</span></span><br><span class="line"> 	<span>golang.org/x/sys</span> <span>v0.42.0</span> <span class="comment">// indirect</span></span><br><span class="line"> 	<span>gopkg.in/yaml.v3</span> <span>v3.0.1</span> <span class="comment">// indirect</span></span><br><span class="line"> )</span><br></pre></td></tr></tbody></table></figure></div>

<p>なるほど、地味だけどこのようにフォーマットされるべきだとは思います。tidyですし。ていうか、以前のバージョンはこう動いてなかったんですね。書いていて思い出しましたが、modfmt のような、require ブロックをマージするようなツールも生まれていましたね。需要はある対応だとは思います。</p>
<h2 id="そもそも、なぜ-require-ブロックは増殖するのか？">そもそも、なぜ require ブロックは増殖するのか？</h2><p>前提となる歴史から振り返ります。</p>
<p>Go 1.17（2021年8月）で module graph pruning（モジュールグラフの枝刈り）が導入されました。これは、ビルドに無関係な依存先の <code>go.mod</code> まで芋づる式にダウンロードして読み込むのを避けるための仕組みで、その代償として、自分の <code>go.mod</code> にビルドに関係する間接依存を従来より網羅的に列挙する必要が生まれました。行数が大きく増えた間接依存に直接依存が埋もれて見通しが悪くならないよう、cmd&#x2F;go: lazy modules: separate section for indirect imports #45965 により、「直接依存のブロック」と「<code>// indirect</code> のブロック」の2つに分けて書く慣例が確立しました（私はそうだったっけ？くらいの記憶しか無いですが）。</p>
<p>問題は、この2ブロック構成が何かの拍子に3つ以上へ増殖することです。#56471 では、増殖の原因として次の3つがあげられています。</p>
<ol>
<li><strong>Gitのマージ</strong>: 2つのブランチが <code>go.mod</code> を変更し、コンフリクトを手動解消する過程でブロックが増える</li>
<li><strong>手動編集ミス</strong>: <code>go get</code> の挙動を把握しきれていないユーザーが、ファイル末尾に <code>require some-module v1.2.3</code> を直接書き足す</li>
<li><strong>go mod tidy自身</strong>: tidyは「すべて直接依存のブロック」と「すべて <code>// indirect</code> のブロック」が1つずつ存在することを期待しており、どちらかが見つからないと<strong>新しいブロックを作ってしまう</strong></li>
</ol>
<p>3つ目は分かりにくいので、実際にやってみます。増殖の最小再現の手順がIssueに投稿されていたので、それをベースにします。まず普通の2ブロック状態を作ります。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go mod init example.com/tidydemo</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">testifyとcolorをimportするmain.goを書いてから</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go mod tidy</span></span><br></pre></td></tr></table></figure>

<div class="code-block"><figure class="highlight gomod"><input type="checkbox" id="code-wrap-h92n71-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-h92n71-2" title="コードの折り返しを切り替える"></label><figcaption><span>go.mod</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">require</span> (</span><br><span class="line">	<span class="string">github.com/fatih/color</span> <span class="type">v1.19.0</span></span><br><span class="line">	<span class="string">github.com/stretchr/testify</span> <span class="type">v1.11.1</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">require</span> (</span><br><span class="line">	<span class="string">github.com/davecgh/go-spew</span> <span class="type">v1.1.1</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">github.com/mattn/go-colorable</span> <span class="type">v0.1.14</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">github.com/mattn/go-isatty</span> <span class="type">v0.0.20</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">github.com/pmezard/go-difflib</span> <span class="type">v1.0.0</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">golang.org/x/sys</span> <span class="type">v0.42.0</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">gopkg.in/yaml.v3</span> <span class="type">v3.0.1</span> <span class="comment">// indirect</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure></div>

<p>ここまでは綺麗です。次に <code>go mod edit -require</code> で依存を追加します。このコマンドは<strong>問答無用で最後のブロックに追記する</strong>仕様のため、間接依存ブロックに直接依存が混ざり込みます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-h92n71-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-h92n71-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go mod edit -require github.com/spf13/cobra@v1.8.1</span></span><br></pre></td></tr></table></figure></div>

<div class="code-block"><figure class="highlight gomod"><input type="checkbox" id="code-wrap-h92n71-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-h92n71-4" title="コードの折り返しを切り替える"></label><figcaption><span>//indirectに直接依存のパッケージが混ざったgo.mod</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">require</span> (</span><br><span class="line">	<span class="string">github.com/fatih/color</span> <span class="type">v1.19.0</span></span><br><span class="line">	<span class="string">github.com/stretchr/testify</span> <span class="type">v1.11.1</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">require</span> (</span><br><span class="line">	<span class="string">github.com/davecgh/go-spew</span> <span class="type">v1.1.1</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">github.com/mattn/go-colorable</span> <span class="type">v0.1.14</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">github.com/mattn/go-isatty</span> <span class="type">v0.0.20</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">github.com/pmezard/go-difflib</span> <span class="type">v1.0.0</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">github.com/spf13/cobra</span> <span class="type">v1.8.1</span>        ← ★混ざった</span><br><span class="line">	<span class="string">golang.org/x/sys</span> <span class="type">v0.42.0</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">gopkg.in/yaml.v3</span> <span class="type">v3.0.1</span> <span class="comment">// indirect</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure></div>

<p>この状態で cobra をimportするコードを追加して <code>go mod tidy</code>（go1.26.5）を実行すると、混在ブロックは「すべて間接依存のブロック」とは見なされないため、cobra が引き込む新しい間接依存（<code>pflag</code> と <code>mousetrap</code>）の置き場として<strong>3つ目のブロックが誕生</strong>します。</p>
<div class="code-block"><figure class="highlight gomod"><input type="checkbox" id="code-wrap-h92n71-5" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-h92n71-5" title="コードの折り返しを切り替える"></label><figcaption><span>3つのブロックになったgo.mod</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">require</span> (</span><br><span class="line">	<span class="string">github.com/fatih/color</span> <span class="type">v1.19.0</span></span><br><span class="line">	<span class="string">github.com/stretchr/testify</span> <span class="type">v1.11.1</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">require</span> (</span><br><span class="line">	<span class="string">github.com/inconshreveable/mousetrap</span> <span class="type">v1.1.0</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">github.com/spf13/pflag</span> <span class="type">v1.0.5</span> <span class="comment">// indirect</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">require</span> (</span><br><span class="line">	<span class="string">github.com/davecgh/go-spew</span> <span class="type">v1.1.1</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">github.com/mattn/go-colorable</span> <span class="type">v0.1.14</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">github.com/mattn/go-isatty</span> <span class="type">v0.0.20</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">github.com/pmezard/go-difflib</span> <span class="type">v1.0.0</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">github.com/spf13/cobra</span> <span class="type">v1.8.1</span></span><br><span class="line">	<span class="string">golang.org/x/sys</span> <span class="type">v0.42.0</span> <span class="comment">// indirect</span></span><br><span class="line">	<span class="string">gopkg.in/yaml.v3</span> <span class="type">v3.0.1</span> <span class="comment">// indirect</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure></div>

<p>たちが悪いのは、ここでもう一度 <code>go mod tidy</code> を実行しても<strong>この3ブロックのまま安定してしまう</strong>ことです（実際に叩きましたが1文字も変わりませんでした）。tidyという名前なのに整頓してくれない、むしろ増やすことすらある、というのがGo 1.26までの姿でした。cmd&#x2F;go: ‘mod tidy’ sometimes adds new, unnecessary require sections #67948 で不具合ではないかと報告もされています。</p>
<h2 id="なぜ自動マージに落ち着いたのか？">なぜ自動マージに落ち着いたのか？</h2><p>「3つ以上のブロックを意図的に使っている人がいたらどうするんだ」という疑問は当然あります。これについて、起票者の mvdan さんはIssue本文で簡易調査をし、Googleのコード検索で世界中のオープンソースの <code>go.mod</code> から3ブロック以上の例を探したところ、見つかったのは4件だけで、どれも意図的には見えなかったと報告しています。原文が率直で良いので引用します。</p>
<blockquote>
<p>The fact that I could only find four examples today in ten minutes of research is a double-edged sword. On one hand it’s proof that basically noone wants more than two sections.<br>（10分の調査で4例しか見つからなかったという事実は諸刃の剣です。一方では、基本的に誰も2つ以上のセクションを望んでいない証拠と言えます。）</p>
</blockquote>
<p>自己申告にある「10分の調査」がポイントで、つまり網羅的な実態調査では無いです。実はMatloobさんも2024年に「module proxy上の <code>go.mod</code> を分析して、3ブロック以上を望む理由が本当にないかを確認すべき」と提案していたのですが、この分析は実施されないまま実装が進みました（これが後述の #80210 に繋がります）。</p>
<p>自動マージではない、代替アプローチとしては、次のような選択肢がありえました。</p>
<ul>
<li><strong>フラグで制御する</strong>: 分割を維持したい人向けの <code>-keep-separate</code> のようなオプション案。後述の #80210 でも言及されています</li>
<li><strong>コメント付きブロックだけ除外する</strong>: 意図的な分割にはコメントが付いているはずだ、という発想。これも #80210 の争点です</li>
</ul>
<p>最終的にGo 1.27では「デフォルトで全部マージする」というもっともシンプルな形が採用されました。理由は次の節で説明します。</p>
<h2 id="コメント付きのブロックはどうなる？">コメント付きのブロックはどうなる？</h2><p>自動マージで一番悩ましいのがコメントの扱いです。</p>
<p>その前に、そもそも <code>go.mod</code> にコメントを書くというイメージ自体、湧きにくいかもしれません（私も普段はほぼ書きません）。用途としては、後述する #80210 にある「誰がメンテしているか」でのグルーピングの他に、既知の不具合を避けるためにあえて古いバージョンへ固定している理由をメモしておく、といった使い方が考えられます。</p>
<p>リリースノートには、コメントは保持され、直接・間接が混在するブロックに付いたコメントはマージ後の直接依存ブロック側に付く、と説明されています。</p>
<p>これも動かして確認します。コメント付きのブロックを意図的に分けた <code>go.mod</code> を用意しました。</p>
<figure class="highlight gomod"><figcaption><span>go.mod</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">require</span> (</span><br><span class="line">	<span class="string">github.com/stretchr/testify</span> <span class="type">v1.11.1</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自分がメンテしているモジュール</span></span><br><span class="line"><span class="keyword">require</span> (</span><br><span class="line">	<span class="string">github.com/fatih/color</span> <span class="type">v1.19.0</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">require</span> (</span><br><span class="line">	<span class="string">github.com/davecgh/go-spew</span> <span class="type">v1.1.1</span> <span class="comment">// indirect</span></span><br><span class="line">	...</span><br><span class="line">)</span><br></pre></td></tr></table></figure>

<p>従来の <code>go mod tidy</code> では、このコメント付きブロックの分割は <strong>そのまま温存</strong>されます）。しかし、 <code>go1.27rc2</code> ではこうなります。</p>
<figure class="highlight gomod"><figcaption><span>go.mod</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment">// 自分がメンテしているモジュール</span></span><br><span class="line"><span class="keyword">require</span> (</span><br><span class="line">	<span class="string">github.com/fatih/color</span> <span class="type">v1.19.0</span></span><br><span class="line">	<span class="string">github.com/stretchr/testify</span> <span class="type">v1.11.1</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">require</span> (</span><br><span class="line">	<span class="string">github.com/davecgh/go-spew</span> <span class="type">v1.1.1</span> <span class="comment">// indirect</span></span><br><span class="line">	...</span><br><span class="line">)</span><br></pre></td></tr></table></figure>

<p>これは、コメントのテキストは保持されていますが、コメントの意味は変わってしまっています。「自分がメンテしているモジュール」というコメントが、自分がメンテしていない testify まで含んだブロックに記載されてしまっているためです。</p>
<p>この点に対して、2026年6月末、rc1リリース直後のタイミングで cmd&#x2F;go: mod tidy unconditionally joining all go.mod “require” sections in Go 1.27 loses intentional grouping #80210 が起票されました。報告者の arp242 さんは、<code>// Things I maintain</code>（自分がメンテしているもの）、<code>// golang.org/x</code>、<code>// Google cloud and its large dependency tree</code> のようにドメインごとにブロックを分けて運用しており、gotipでtidyしたらすべて失われた、という報告です。主張も明快です。</p>
<blockquote>
<p>Personally I rarely care if something is a direct or indirect dependency – I care about who maintains it: our team, me, the Go team, or some random internet person.<br>（個人的には直接依存か間接依存かはほとんど気にしていません。気にするのは誰がメンテナンスしているかです。自分たちのチームか、自分か、Goチームか、それともどこかのインターネットの誰かか。）</p>
</blockquote>
<p>そして「コメント付きのセクションは意図的に作られたと見なして触らない」という折衷案を提示しています。これに対するMatloobさんの応答が興味深く、まず調査不足を率直に認めています。</p>
<blockquote>
<p>You’re right that we didn’t do the analysis for this. This is exactly the kind of feedback we’re looking for.<br>（この件について分析を行わなかったというのはその通りです。これはまさに私たちが求めていた類のフィードバックです。）</p>
</blockquote>
<p>その上で、コメントなしブロックの扱いについては明言しています。</p>
<blockquote>
<p>We’re definitely going to keep the behavior of folding blocks that don’t have comments for Go 1.27.<br>（コメントのないブロックを畳み込む挙動は、Go 1.27で確実に維持します。）</p>
</blockquote>
<p>つまり争点は「コメント<strong>付き</strong>ブロックを畳むか（rc2の挙動）、触らないか（1.26の挙動）」に絞られています。arp242 さんは「事故で増えたブロックにはほぼコメントが付いていないはずなので、コメント付きだけ除外すれば両取りできる（I think we can have our cake and eat it too here）」と主張し、逆に元Issue側の立場の thediveo さんは全部畳んで欲しい派で、必要なら <code>-keep-separate</code> フラグでも良いとコメントしています。オプションをつけないのがGoの良いところだと思っている派なので、どうにか上手くまとまることを祈りたい..。</p>
<p>本記事執筆時点（2026年7月末、go1.27rc2）でこのIssueはOpenのままです。<strong>正式リリースでコメント付きブロックの挙動が変わる可能性がある</strong>ため、この記事も正式リリース時に追記予定です。もし「コメント＝保護シグナル」の折衷案が採用されれば、意図的なグルーピングにはブロックコメントを付けておく、という運用が定着するかもしれません。</p>
<h2 id="FAQ">FAQ</h2><p><strong>Q. 今までの互換性を保ちたい場合の回避策はあるの？</strong></p>
<p>Go 1.27にあげつつ、ブロックの分割を守る方法は私の理解では存在しません。正確に言うと先述の通り、#80210 の結果次第です。</p>
<p><strong>Q. そもそも増殖の一因である go mod edit 側の仕様を直せば良いのでは？</strong></p>
<p><code>go mod edit -require</code> が最後のブロックに追記する挙動は、1.27rc2でも変わっていません。そもそも増殖は <code>go mod edit</code> だけでなく、手動編集やGitのコンフリクト解消の過程でも発生するため、<code>go mod edit</code> を直しても問題は完全には解消しません。また、 <code>go mod tidy</code> でフォーマットを行う方法は <code>gofmt</code> と似ているように思え、どのようなツールやコード生成が吐くコードが不揃いでも、フォーマッタを通せば単一の形式に収束するという構図はGoらしさを感じました。</p>
<p><strong>Q. 直接依存・間接依存が混在したブロックについたコメントを、”直接”依存側に必ずつける仕様でなぜ大丈夫なのか？</strong></p>
<p>間接依存の行は <code>go mod tidy</code> が機械的に出し入れするもので、人間がコメントを書き添える対象は基本的に自分の意思で追加した直接依存のはずだ、という想定が背景にあると読み取れます。実際 #80210 でも「ブロックにはコメントを付けておらず、個々のモジュール行にだけ付けている」「ブロックレベルのコメントは使ったことがない」という声が寄せられており、間接依存側にブロックコメントが書かれるケースは相当に稀のようです。とはいえ「間接側にコメントは無いだろう」という想定で一律に寄せてしまうのは、互換性に慎重なGoチームにしては珍しく強気な設計判断で、個人的には意外に感じています。</p>
<h2 id="さいごに">さいごに</h2><p>Go 1.27の <code>go mod tidy</code> の require ブロック自動マージについて紹介しました。</p>
<p>個人的には互換性を重視すると思っているGoチームとして意外な動きな気がしており、少しドキドキのアップデートです。#80210の決着がどうなるか楽しみです。</p>
]]></content>
    <summary type="html">Go 1.27でgo mod tidyに追加されたrequireブロックの自動マージについて紹介します。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.27" scheme="https://future-architect.github.io/tags/Go1-27/"/>
    <category term="GoModules" scheme="https://future-architect.github.io/tags/GoModules/"/>
  </entry>
  <entry>
    <title>Go 1.27リリース連載：インデックス+HTTP/3(定期観察)+SIMD(第2弾)</title>
    <link href="https://future-architect.github.io/articles/20260728a/"/>
    <id>https://future-architect.github.io/articles/20260728a/</id>
    <published>2026-07-27T15:00:00.000Z</published>
    <updated>2026-07-27T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260728a/top.jpg" alt="" width="720" height="393">

<p>The Go gopher was designed by Renee French.</p>
<p>Go 1.27もそろそろ近づいてきたようです。Future Tech Blog恒例のGoリリース連載です。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="center">日付</th>
<th align="left">タイトル</th>
<th align="left">著者</th>
</tr>
</thead>
<tbody><tr>
<td align="center">2026&#x2F;7&#x2F;28</td>
<td align="left">インデックス記事＋HTTP3&#x2F;SIMD(この記事)</td>
<td align="left">渋川</td>
</tr>
<tr>
<td align="center">2026&#x2F;7&#x2F;29</td>
<td align="left">go mod tidy</td>
<td align="left">真野隼記</td>
</tr>
<tr>
<td align="center">2026&#x2F;7&#x2F;30</td>
<td align="left">ジェネリクスメソッド</td>
<td align="left">市川裕也</td>
</tr>
<tr>
<td align="center">2026&#x2F;7&#x2F;31</td>
<td align="left">json v2</td>
<td align="left">辻大志郎</td>
</tr>
<tr>
<td align="center">2026&#x2F;8&#x2F;3</td>
<td align="left">ゴルーチンリークプロファイル (goroutineleak) を活用したデバッグ</td>
<td align="left">棚井龍之介</td>
</tr>
<tr>
<td align="center">2026&#x2F;8&#x2F;4</td>
<td align="left">uuidベンチマーク</td>
<td align="left">武田大輝</td>
</tr>
<tr>
<td align="center">2026&#x2F;8&#x2F;5</td>
<td align="left">埋め込みフィールドの初期化が楽になった</td>
<td align="left">真野隼記</td>
</tr>
<tr>
<td align="center">2026&#x2F;8&#x2F;6</td>
<td align="left">ポスト量子暗号「crypto&#x2F;mldsa」パッケージ</td>
<td align="left">棚井龍之介</td>
</tr>
<tr>
<td align="center">2026&#x2F;8&#x2F;7</td>
<td align="left">go fix</td>
<td align="left">真野隼記</td>
</tr>
</tbody></table></div>
<h2 id="別記事で取り上げる以外の更新">別記事で取り上げる以外の更新</h2><p>betaやRCが出る前から話題沸騰のUUIDv7対応については、convtoさんがすでに詳しく解説を書いてくださいっているのでそちらを参照されるとよいかと思います。</p>
<ul>
<li>Go 1.27 から uuid 実装がサポートされる！ので個人的に気になった議論とその着地をまとめてみた</li>
</ul>
<p>ざっと変更があったところで、細かいところを除くとこんなところかなと思います。</p>
<ul>
<li>80バイト未満のメモリ割り当てコストを最大30%削減</li>
<li>量子コンピュータで計算力が上がっても大丈夫になることを見越した、ポスト量子暗号のcrypto&#x2F;mldsaパッケージの追加</li>
<li>compress&#x2F;flate(zipの中で使われる圧縮アルゴリズム)が高速化</li>
<li>net&#x2F;httpのHTTP&#x2F;2サーバーがクライアントの優先度を考慮するようになった</li>
<li>net&#x2F;http&#x2F;httptestのNewServerは127.0.0.1の空きポートを使った実際に通信するサーバーだったが、オンメモリで通信し、testing&#x2F;synctestとの親和性の高いNewTestServer()が追加</li>
<li>念入りに実行前後でメモリを消去して、取り扱った機密情報が他のgoroutineからアクセスされないようにする<code>runtime/secret</code>パッケージが追加</li>
<li><code>bytes</code>と<code>strings</code>に、最後にマッチした文字列でカットして、その前後を返す<code>CutLast()</code>を追加</li>
</ul>
<h2 id="HTTP-3はどうか？">HTTP&#x2F;3はどうか？</h2><p>3年前のエントリーでGo本体のHTTP3対応の計画を紹介しました。</p>
<ul>
<li>最終的にはnet&#x2F;quicが作られる</li>
<li>ただし、APIの安定化のために、まずは準標準パッケージとして golang.org&#x2F;x&#x2F;net&#x2F;quicを作っていく</li>
<li>github.com&#x2F;quic-go&#x2F;quic-go という実装はあるが、それをそのまま取り込むことはしない</li>
</ul>
<p>現在もquicの実装は粛々と進んでいます。</p>
<ul>
<li>golang.org&#x2F;x&#x2F;net&#x2F;quic</li>
</ul>
<p>また、テスト用の実装しかなさそうですが、4月にhttp3パッケージも追加されました。</p>
<ul>
<li>golang.org&#x2F;x&#x2F;net&#x2F;http3</li>
</ul>
<p>JavaがJDK26すでにリリースしてしまったのでプログラミング言語でHTTP&#x2F;3一番乗り、というわけにはいかないのですが楽しみですね。</p>
<h2 id="SIMD">SIMD</h2><p>まだ実験的サポートで、<code>GOEXPERIMENT=simd</code>という環境変数がないと使えない機能です。<br>前回の連載でも取り上げましたが、SIMDは今回もアップデートがありました。</p>
<ul>
<li>Go 1.26連載：インデックス＋SIMD</li>
</ul>
<h3 id="simd-simdarchパッケージ">simd&#x2F;simdarchパッケージ</h3><p>前回のときはsimd&#x2F;simdarchパッケージが追加されたという内容を書きましたが、この時はIntel系のSIMD命令(128ビット、256ビット、512ビット)のみに対応していました。今回、1.27ではARMのNeonとWASM(ともに128ビット)にも対応しました。</p>
<p>こちらは今後もアーキテクチャが増えるが、APIは安定ではないとされています。</p>
<p>実際、1.26のときの<code>Store(*[4]int32)</code>は<code>StoreArray(*[4]int32)</code>に変わり、<code>StoreSlice([]int32)</code>が<code>Store([]int32)</code>に変わって引数の型が変わったことで、以前作ったサンプルが動かなかったり、ARMでは動いたものの<code>archsimd.Int16x8</code>に<code>DotProductPairs()</code>メソッドがないというエラーが出たりしました。アーキテクチャごとにもサポートしている内容が変わっています。</p>
<p>こちらのパッケージはともかく「アーキテクチャの性能を引き出す低レベルパッケージ」ということで、simd&#x2F;archsimdのプロポーザルでは<code>syscall</code>パッケージ相当という扱いです。</p>
<h3 id="simdパッケージ">simdパッケージ</h3><p>今回はその上により抽象度の高い<code>os</code>パッケージ相当のsimdパッケージが追加されました。</p>
<p><code>simd/simdarch</code>を見た後にこちらを見ると「？？？？」となること間違いなしです。APIを見ても扱うビット数の情報がどこにもない。32ビット浮動小数点数を扱うデータ型を全部出してみると、<code>simd</code>パッケージの扱うデータサイズが見えてこないですよね。</p>
<ul>
<li><code>simdarch.Float32x4</code></li>
<li><code>simdarch.Float32x8</code></li>
<li><code>simdarch.Float32x16</code></li>
<li><code>simd.Float32s</code></li>
</ul>
<p>というのも、こちらは実行環境に合わせて扱うビット数が変わるAPIなっています。simdのプロポーザルを見ると、「そのCPUが扱える最大のビット数を扱えるのがとりあえずベストだろう」ということです。</p>
<p>実際、今回新しく作られた型で確認するとM3のARMのmacだと1.27のリリースノート通りに128ビットであることがわかります。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-eup45r-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-eup45r-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// ビット数を取得</span></span><br><span class="line">width1 := simd.VectorBitSize()</span><br><span class="line">fmt.Printf(<span class="string">&quot;vector width: %d bits\n&quot;</span>, width1)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Int8s: 16要素数, 128ビット</span></span><br><span class="line">width2 := simd.Int8s&#123;&#125;.Len()</span><br><span class="line">bits := width * <span class="number">8</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;Int8s: %d elements, %d bits\n&quot;</span>, width2, bits)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">()</span></span> <span class="type">int</span></span><br></pre></td></tr></table></figure></div>

<p>実際どの様に使うかというと、指定されたスライスから値を読み込んで、SIMDのメモリ状況を再現した配列のような型と、それに含まれる読み込んだ個数を返す<code>LoadXXXPart()</code>というファクトリ関数を使ってSIMDで扱える単位でデータを切り出します。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-eup45r-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-eup45r-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Process</span><span class="params">(s []<span class="type">int8</span>)</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> <span class="built_in">len</span>(s) &gt; <span class="number">0</span> &#123;</span><br><span class="line">        <span class="comment">// 読み込んでInt8sのvを作成性。n個の要素を読み込んだ</span></span><br><span class="line">        v, n := simd.LoadInt8sPart(s)</span><br><span class="line">        <span class="comment">// 読み込んだ次からまた再実行</span></span><br><span class="line">        s = s[n:]</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>前回扱ったベクトル検索では、ベクトルは768次元でした。このような多次元のデータを扱う場合は、環境によって16個単位、32個単位、64個単位という個数が変わっても似た様な感じでループでデータを扱うのであまり問題は発生しないでしょう。</p>
<p>ちょっとおもしろかったのは、<code>simd.Emulated() bool</code>というエミュレーションでも動作するというポイントですね。</p>
<h2 id="とはいえ、これはベストなのか？">とはいえ、これはベストなのか？</h2><p>この様な仕組みで、例えば座標変換でflaot32が入った4行4列の行列を使いたいとします。これをこの<code>simd</code>パッケージで扱うとします。128ビットであれば32x4なのでうまく和や積の計算は無駄なく行えますが、512ビットアーキテクチャだったらどうなるでしょうか？</p>
<p>それは開いた領域はゼロ埋めをして計算して有効な箇所だけ取り出すという処理になります。先ほども利用した<code>Load型名Part()</code>というファクトリ関数は、入力のデータ数がすくなければゼロ埋めをすると書かれています。そうなると、単にメモリ効率が1&#x2F;4に低下するだけです。先ほどの長大なベクトルを扱うには問題はありませんが、小さい固定長のベクトルではいまいちかもしれません。</p>
<p>もっとも、2つ、4つの座標の計算をまとめて一度に計算するというロジックを作れば効率は良いです。こういう使い方をしてもらいたいのかもしれません。実際、ビット数ごとにロジックを何種類も作るよりかは楽ですね。</p>
<p>もう1つの問題は実行効率です。</p>
<p>AVX512に対応しているからといって、512ビット演算が最速とは限らないケースがあります。64ビットの普通の四則演算と比べると、ビット数が多い分、CPUの中の実装面積が増えます。しかもそんなに四六時中動いているわけじゃない。特に、最近のbig.LITTLE構成を取るインテルのCPUの場合、消費電力が少ない方のコアに委譲が発生します。命令セットが異なるCPUをスムーズにやりとりするのは困難なためか、最近のインテルでは512ビットの取り扱いを省いています。</p>
<p>そういう節約は他社でも行われていて、AMDは256ビットのAVX演算機を2つ組み合わせて512ビットを実現しています。この場合、256ビットが一番効率が良いということもありえます。そのあたりは以下の記事が詳しいです。</p>
<ul>
<li>ローカルLLM向けCPU選定｜Intel&#x2F;AMDのAVX-512対応状況と推論速度の比較</li>
</ul>
<p>Goの処理系はおそらく、CPUの命令セットをサポートしているかどうかで扱うビット数を変えていると思いますが、必ずしも裏の仕組みはCPUの情報には乗らないはずなので、</p>
<h2 id="まとめ">まとめ</h2><p>相変わらず、知る人ぞ知るというか、わかっている人向けなSIMDですが、<code>simd</code>パッケージのおかげでだいぶ扱いやすくなってきました。そこそこ高水準で扱いやすい言語だけどSIMDもだいぶ扱いやすい（アセンブラを書くよりは）ということで、アルゴリズムを色々作って比較してみる試作環境としてはだいぶ扱いやすいと思います。多少効率の面では気になったりもしますが、まあ最新のCPU使ってね、ってことですかね。</p>
<p>標準ライブラリではこのようなSIMDの活用なケースはアセンブラでガリガリ書かれていましたがなかなか真似したりやってみるのは困難でした。持っていないアーキテクチャのテストなんかはどうしても雑になってしまいがちでした、サードパーティ含めて今後はSIMDを活用して効率が上がるケースも増えそうです。クロスコンパイルとかも楽ですしね。</p>
]]></content>
    <summary type="html">Go 1.27もそろそろ近づいてきたようです。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.27" scheme="https://future-architect.github.io/tags/Go1-27/"/>
    <category term="HTTP" scheme="https://future-architect.github.io/tags/HTTP/"/>
    <category term="SIMD" scheme="https://future-architect.github.io/tags/SIMD/"/>
    <category term="インデックス" scheme="https://future-architect.github.io/tags/%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9/"/>
  </entry>
  <entry>
    <title>よくあるファイルアップロードの実装の共通コンポーネントを実装してみました</title>
    <link href="https://future-architect.github.io/articles/20260714a/"/>
    <id>https://future-architect.github.io/articles/20260714a/</id>
    <published>2026-07-13T15:00:00.000Z</published>
    <updated>2026-07-13T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<p>今まで色んな案件で同じようなファイルアップロード処理を見てきたので、共通のコンテナにできたらいいかも、と思ってAIに作ってもらいました。</p>
<ul>
<li>https://github.com/shibukawa/streamuploader</li>
</ul>
<p>よくある要件というのは、</p>
<ul>
<li>フロントからアップロードされたファイルはS3に置く。できれば一度tmpに書いたりせずにストリーミングで処理したい</li>
<li>ファイルアップロード機能をウェブアプリで提供したいがウィルススキャンを必ずする</li>
</ul>
<p>よくある方の前者はフレームワーク側で一度ローカルのtmpに置くとストレージの小さい(tmpがtmpfsだったりする)コンテナとかLambdaで動かすとちょっと嬉しく無いかもねという感じです。</p>
<p>よくある方の後者はだいたい「ビジネス要件」というよりもセキュリティ要件みたいなIT部門の受け入れ要件になっていますね。おそらく、根拠はOWASP Application Security Verification Standardでは無いかと思います。最新の5.0だと5章のFile Handlingにいくつか項目がありますね。</p>
<p>これをよく読むと、アップロードしたファイルをユーザーがダウンロードするような要件がなければ、例えば、アップロードされたCSVファイルをパースして取り込むみたな機能であればウィルススキャンは不要そうです。とはいえ、「これがルールです。ダメなら受け入れられません」と言われたらまあ対応せざるを得ないこともあるかもしれません。</p>
<p>AWSにもGuard Dutyがあってウィルススキャン流行ってくれる機能はあるのですがアップロードしたあとにしばらくしたらタグがつくという機能です。そのタグをEventBridgeで拾って・・・みたいな感じですが、可能ならチェック済みのみファイルのみが上がって欲しいというか非同期処理が挟まると面倒ですよね。</p>
<img fetchpriority="high" src="/images/2026/20260714a/スクリーンショット_2026-07-09_6.18.40.png" alt="スクリーンショット_2026-07-09_6.18.40.png" width="1200" height="491">

<p>それ以外にも結構ヘビーな要件が結構多いですね。</p>
<ul>
<li>5.2.1	アプリケーションが処理できるサイズのファイルのみを受け入れること</li>
<li>5.2.2	アプリケーションがファイルを単独で、または zip ファイルなどのアーカイブ内で受け入れた際に、ファイル拡張子が期待されるファイル拡張子と一致しているかを確認し、内容が拡張子が表すタイプと一致しているかを検証する</li>
<li>5.2.3	アプリケーションが圧縮ファイル（例：zip、gz、docx、odt）を許容される最大未圧縮サイズおよび最大ファイル数に対してチェックしていることを確認し、ファイルを解凍する前に確認する</li>
<li>5.2.4	単一のユーザーが過剰なファイルや過度に大きなファイルでストレージを埋められないよう、ファイルサイズクォータとユーザーあたりの最大ファイル数が適用されることを確認する</li>
<li>5.2.5	アプリケーションが、特に必要な場合を除き、シンボリックリンクを含む圧縮ファイルのアップロードを許可していないことを確認する</li>
<li>5.2.6	アプリケーションが、許容される最大ピクセルサイズを超えるピクセルサイズのアップロード画像を拒否していることを確認する</li>
</ul>
<p>毎回みんながそれを実装するのは大変そうなので、共通ミドルウェア化したら良さそうだなと思いました。</p>
<h2 id="作ってみたもの">作ってみたもの</h2><p>StreamUploaderという名前で作ってみました。システム構成としてはこんな感じです。ファイルアップロード前にアップロードキーを取得します。そのキーを使ってStreamUploaderにファイルの実体を送ります。</p>
<img src="/images/2026/20260714a/スクリーンショット_2026-07-09_6.18.20.png" alt="スクリーンショット_2026-07-09_6.18.20.png" width="1187" height="460" loading="lazy">

<p>裏では以下のようなチェックなどをします。</p>
<ul>
<li>S3に投げる前にチェックをする(除外ファイル形式とかは設定変更可)<ul>
<li>事前にマジックバイト(先頭の方)を見てコンテンツ内容との不一致のチェックや許可されているコンテンツかどうかなどをチェック</li>
<li>圧縮ファイルの中のチェック</li>
<li>zipbombチェック</li>
<li>xlsxやPDFはマクロやJSなどの動的要素をエラーする</li>
<li>画像ファイルは回転情報とカラープロファイル以外のプライバシーの問題がありそうなEXIF情報削除</li>
<li>ClamAVでウィルススキャン</li>
</ul>
</li>
<li>非同期でコンテンツ作成<ul>
<li>サムネイル作成</li>
<li>OCRやオフィスファイルのテキスト抽出</li>
</ul>
</li>
</ul>
<p>zipファイルは仕方ないので全量ダウンロードしてからのチェックをしていますが、ClamAVのウィルススキャンの間、ファイルをオンメモリに持ち続けるとメモリを消費してしまうので、MultiWriterでS3アップロードと、ClamAVのTCPのAPIに並行で投げて、正常なら最終的な名前にリネーム、エラーがあったらS3側を削除という形にしています。</p>
<p>検索エンジンなども入れたらいいかと思ったのですが、アプリごとのカスタマイズとか多そうなのでテキストの抽出ぐらいにしています。</p>
<h2 id="使い方">使い方</h2><p>フロントエンドからすると、StreamUploaderにfetch()でファイルを送ると、ファイルタイプのチェックやウィルスチェックがその場で行われ、4xxエラーが返ります。GuardDutyだと送信そのものは成功し、結果は別途非同期で取ってこないといけないので、それと比べるとだいぶシンプルです。</p>
<p>アップロードキーはちょっと面倒に見えますが、S3アップロードにSigned URLを使うのとほぼ変わらないですし、ファイルドロップをされた瞬間に取得してファイルを送信してキーを保持しておく感じですね。そして確定されたタイミングでそれをバックエンドに成功したキーだけ送ると、例えばファイル本体とそのファイルの説明を一緒に送るみたいなところはシンプルにできます。</p>
<p>なおファイル名やcontent-typeはキー取得時に送っています。というのも、FileオブジェクトをFormDataでラップして送ると、サーバー側でストリーミング処理しにくいです。デコード処理をしないといけないので。Fileオブジェクトをそのまま<code>fetch()</code>の<code>body</code>に渡せばファイルの中身だけを送ることになるのでサーバー側でストリーミングで処理しやすくなります。</p>
<div class="code-block"><figure class="highlight js"><input type="checkbox" id="code-wrap-260qok-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-260qok-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">fileInput.<span class="title function_">addEventListener</span>(<span class="string">&quot;change&quot;</span>, <span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> file <span class="keyword">of</span> <span class="title class_">Array</span>.<span class="title function_">from</span>(fileInput.<span class="property">files</span> || [])) &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">startUpload</span>(file);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">startUpload</span>(<span class="params">file</span>) &#123;</span><br><span class="line">  <span class="comment">// キー取得</span></span><br><span class="line">  <span class="keyword">const</span> keyResp = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="title function_">uploadEndpoint</span>(<span class="string">&quot;/keys&quot;</span>), &#123;</span><br><span class="line">    <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>,</span><br><span class="line">    <span class="attr">headers</span>: &#123;<span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span>&#125;,</span><br><span class="line">    <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;<span class="attr">file_name</span>: file.<span class="property">name</span>, <span class="attr">content_type</span>: file.<span class="property">type</span>, <span class="attr">size_bytes</span>: file.<span class="property">size</span>&#125;)</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">const</span> key = <span class="keyword">await</span> <span class="title function_">readJSONOrText</span>(keyResp);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ファイル送信</span></span><br><span class="line">  uploadKey = key.<span class="property">upload_key</span>;</span><br><span class="line">  <span class="keyword">const</span> putResp = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="title function_">uploadContentEndpoint</span>(key), &#123;</span><br><span class="line">    <span class="attr">method</span>: <span class="string">&quot;PUT&quot;</span>,</span><br><span class="line">    <span class="attr">headers</span>: &#123;<span class="string">&quot;Content-Type&quot;</span>: file.<span class="property">type</span> || <span class="string">&quot;application/octet-stream&quot;</span>&#125;,</span><br><span class="line">    <span class="attr">body</span>: file</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>他にも、<code>fetch()</code>だとやりにくいファイルアップロード進捗を別途WebSocketで返すエンドポイントがあったりします。</p>
<h2 id="まとめ">まとめ</h2><p>結構、仕事の中だと「時間がないから最低限で」という感じで済ますということが結構多いんじゃないかなと思います。生成AIのおかげでそういうアイディアが形にしやすくなったのは良いですね。</p>
<p>まあみんながこれを使うというよりも、きっとこのアイディアを元にみんな各々実装するという形になるかもしれませんが、きちんと実証実験を行なっておけるのは単に頭の中でずっと考えているだけよりもずっとよいですね。スッキリしました。</p>
<p>サムネイル作成はGoのライブラリがあるものはそれを使い、CLIでオフロードできるものは積極的に使うみたいな感じでやっています。ただ、Officeファイルのサムネイル作成。LibreOfficeを入れるとイメージサイズが1GB増えちゃうので、そこもなんかライブラリ作りたい気がしています。</p>
]]></content>
    <summary type="html">今まで色んな案件で同じようなファイルアップロード処理を見てきたので、共通のコンテナにできたらいいかも、と思ってAIに作ってもらいました。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Docker" scheme="https://future-architect.github.io/tags/Docker/"/>
    <category term="Lambda" scheme="https://future-architect.github.io/tags/Lambda/"/>
    <category term="S3" scheme="https://future-architect.github.io/tags/S3/"/>
  </entry>
  <entry>
    <title>Gopher が Rust に入門して感じた Go との違いについて</title>
    <link href="https://future-architect.github.io/articles/20260511a/"/>
    <id>https://future-architect.github.io/articles/20260511a/</id>
    <published>2026-05-10T15:00:00.000Z</published>
    <updated>2026-05-10T15:00:00.000Z</updated>
    <author><name>市川裕也</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260511a/top.jpg" alt="" width="900" height="502">

<p>春の入門祭り2026の8日目の記事です。</p>
<h2 id="はじめに">はじめに</h2><p>こんにちは、CSIG (Cyber Security Innovation Group) の市川です。普段は FutureVuls の開発を主に担当しており、最近はパフォーマンスの課題に重点的に取り組んでいます。</p>
<p>業務では主に Go を書いているのですが、「Rust は面白い」という話は方々から聞いており、自分もどこかで学んでおきたい、という気持ちがありました。</p>
<p>そこで、せっかくの入門祭りということで、これを機に Rust に入門してみることにしました。Rust を学習して簡単な Web アプリケーションを作成してみたので、その感想を共有しようと思います。</p>
<h2 id="ブログの内容">ブログの内容</h2><p>本記事では、以下の内容についてお話しします。</p>
<ul>
<li>どのように学習を進めたか</li>
<li>Rust の言語仕様で「優れている」と感じた点</li>
<li>Go を書く時に感じる課題を Rust がどう解決してくれそうか</li>
<li>Go と Rust で簡単な Web アプリケーションを作ってみた感想</li>
</ul>
<div class="note-container note-warn"><span class="note-icon"></span><div>

<p>全体的に、 Rust についての知識が不十分故に、不正確な記述となっている可能性があります。あらかじめご了承ください。<br>間違った記述に気づいた方は、ご指摘いただけますと大変幸いです。</p>
</div></div>

<h2 id="どのように学習を進めたか">どのように学習を進めたか</h2><p>『The Rust Programming Language 日本語版』を読みつつ、読むだけでは分からない部分については写経することで学習を進めました。<br>https://doc.rust-jp.rs/book-ja/title-page.html</p>
<p>Go との違いを理解したい気持ちが強かったので、Go に存在しない「所有権」「enum」「Result 型」「ライフタイム」あたりを重点的に読みました。</p>
<p>以下の内容はほぼ触れていないため、時間がある時に読みたいなと思います。</p>
<ul>
<li>スマートポインタ</li>
<li>並行処理</li>
<li>マクロ</li>
<li>etc…</li>
</ul>
<p>読むだけでは理解が追い付かなかった部分については、以下の動画も参照しました。<br>『The Rust Programming Language 日本語版』を丸々解説してくださっている素晴らしい動画でした。<br>https://www.youtube.com/watch?v=tw2WCjBTgRM</p>
<h2 id="Rust-の優れていると感じた点">Rust の優れていると感じた点</h2><p>ここからは、 Rust に触れてみた感想をつらつらと記載していきます。</p>
<h3 id="所有権・参照が、メモリ安全性を担保してくれている">所有権・参照が、メモリ安全性を担保してくれている</h3><p>Rust のメモリ安全性を担保するための核となっている仕様として、「所有権」「参照」というものがあります。<br>これらの概念を理解したい方は、『The Rust Programming Language 日本語版』などを読んでいただければと思います。<br>自分の理解を非常にざっくり以下に記載しておきます。</p>
<ul>
<li>所有権<ul>
<li>Rust の各値には、所有者が存在する</li>
<li>値を代入したり、関数に値を渡したりすると、所有権が移動 (ムーブ) する</li>
<li>いかなる時も、所有者はひとりである</li>
<li>所有権が移動すると、元の変数は使用できなくなる</li>
<li>所有者がスコープから外れると、その値は破棄 (drop) される</li>
<li><strong>使用できなくなった変数を使用しようとすると、コンパイルエラーになる</strong></li>
</ul>
</li>
</ul>
<div class="code-block"><figure class="highlight rs"><input type="checkbox" id="code-wrap-hhte3j-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-hhte3j-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">x</span> = <span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="keyword">let</span> <span class="variable">y</span> = x;</span><br><span class="line"></span><br><span class="line"><span class="built_in">println!</span>(<span class="string">&quot;x: &#123;&#125;&quot;</span>, x); <span class="comment">// x の所有権が y にムーブしているので、これはコンパイルエラーになる</span></span><br></pre></td></tr></table></figure></div>

<ul>
<li>参照<ul>
<li>値をいちいちムーブさせるのが面倒くさい時は、参照を使う</li>
<li>参照には、不変参照と可変参照が存在する。不変参照は <code>s: &amp;String</code>、可変参照は <code>s: &amp;mut String</code> みたいな書き方をする</li>
<li>ある瞬間において、ひとつの可変参照、または任意個の不変参照のどちらかしか作成できない</li>
<li>可変参照が有効な間は、元の値にもアクセスできなくなる (読み取りも書き込みも不可)</li>
<li><strong>可変参照が作成されている間に不変参照を作成したり、元の値にアクセスしようとすると、コンパイルエラーになる</strong></li>
</ul>
</li>
</ul>
<p>Rust が素晴らしいなと感じたのは、所有権や参照のルールを破ろうとした場合に、 <strong>コンパイルエラーになる</strong> ということです。<br>Go の永遠の課題である、「想定外の値の変更」や「nil アクセスエラー」を、コンパイルの時点で防いでくれるのには、感動を覚えました。</p>
<h3 id="enum-があり、排他的な処理を書きやすい">enum があり、排他的な処理を書きやすい</h3><p>Rust の enum は、各値がデータを持つこともできる「代数的データ型」として実装されています。<br>これにより、 <code>match</code> 式で網羅性のチェックが効くため、 「特定の値に対する排他的な処理」 を安全に書けるようになっています。<br>また、 値の有無を表す <code>Option</code> 型やエラーを表現する <code>Result</code> 型もこの enum の仕組みの上に成り立っており、 null 処理やエラー処理を型システム上で安全に書くための基盤となっています。 (Go との比較の節でもこの話をします)</p>
<p>enum が、メモリ安全の担保や nil の排除において大きな役割を果たしているのが、面白いなと感じました。</p>
<h3 id="nil-が存在しない">nil が存在しない</h3><p>Rust には、なんと nil が用意されていません。 今まで nil が存在しない言語に出会ったことがなかったため、非常に驚きました。<br>(生ポインタを unsafe ブロック内でデリファレンスすることで nil 相当の挙動を扱うことはできるそうですが、未学習のため本記事ではスキップします)<br>では、どのように「値が存在しないことを表すか」というと、 <code>Option</code> という enum を用います。</p>
<figure class="highlight rs"><table><tr><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Option</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="literal">None</span>,</span><br><span class="line">    <span class="title function_ invoke__">Some</span>(T),</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>このように nil 相当のものをそもそも扱わせない仕様になっているのは、メモリ安全性を高める上で非常に有効であり、かなり好感を持ちました。<br>Go の nil 問題との具体的な比較は、 後の節「<code>Option</code> 型 + nil が存在しないこと: nil アクセスをコンパイラが防いでくれる」で扱います。</p>
<h2 id="Go-を書く時に感じる課題をどう解決してくれそうか">Go を書く時に感じる課題をどう解決してくれそうか</h2><p>「Rust のこの仕様により、Go を書いていてよく感じる課題が解決されるのでは？」と感じる点がいくつかあったため、<br>この節では、「Go の課題に対するアプローチ」という観点で Rust の良いと思った点を書いていきます。</p>
<h3 id="不変参照-「値が変更されないこと」を明示できる">不変参照: 「値が変更されないこと」を明示できる</h3><p>Go を書く際、大きな構造体が毎回コピーされるのを防ぐため、構造体のポインタを関数の引数に渡したいケースはよくあるかと思います。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-hhte3j-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-hhte3j-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> Hoge <span class="keyword">struct</span> &#123;</span><br><span class="line">    fuga <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ProcessHoge</span><span class="params">(hoge *Hoge)</span></span> &#123;</span><br><span class="line">    <span class="comment">// Hoge に対してなんらかの操作をする</span></span><br><span class="line">    hoge.fuga = <span class="number">10000</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    hoge := Hoge&#123;fuga: <span class="number">1</span>&#125;</span><br><span class="line">    ProcessHoge(&amp;hoge) <span class="comment">// hoge に対して、 ProcessHoge の中でどんな操作が行われるか分からない</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>このようなケースの大きな問題として、「 <code>ProcessHoge</code> の先で <code>Hoge</code> が変更されるかが分からない」といったものが挙げられます。</p>
<p>Rust では、参照が可変か不変かを記述できるため、以下の <code>not_change_hoge</code> のように、「この関数では <code>hoge</code> を変更しません」という仕様を明示できます。</p>
<p>引数を見ただけで、その引数が変更される可能性があるかどうか分かるというのは、可読性や保守性の観点で非常にありがたいです。</p>
<p>この仕様は、「 <code>ProcessHoge</code> の先で <code>Hoge</code> が変更されるかが分からない」といった課題を解決してくれるものだと感じました。</p>
<div class="code-block"><figure class="highlight rs"><input type="checkbox" id="code-wrap-hhte3j-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-hhte3j-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Hoge</span> &#123;</span><br><span class="line">    fuga: <span class="type">i32</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">process_hoge</span>(hoge: &amp;<span class="keyword">mut</span> Hoge) &#123;</span><br><span class="line">    hoge.fuga = <span class="number">10000</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">not_change_hoge</span>(hoge: &amp;Hoge) &#123;</span><br><span class="line">    <span class="comment">// 不変参照を受け取っているので、 hoge を変更できない</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">hoge</span> = Hoge &#123; fuga: <span class="number">1</span> &#125;;</span><br><span class="line">    <span class="title function_ invoke__">process_hoge</span>(&amp;<span class="keyword">mut</span> hoge);</span><br><span class="line">    <span class="title function_ invoke__">not_change_hoge</span>(&amp;hoge);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h3 id="enum-排他的な処理をコンパイラが担保してくれる">enum: 排他的な処理をコンパイラが担保してくれる</h3><p>Go には enum が存在しません。</p>
<p>そのため、 enum 的な「特定の複数個の値」に対して、排他的な処理を行えているかが、言語仕様としては担保されないという課題があります。</p>
<p>例として、 bob という Go の ORM が、データベースの enum 型のカラムを Go のコードとして表すために生成する、以下のような型を考えます。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-hhte3j-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-hhte3j-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// Enum values for TodoStatus</span></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">    TodoStatusPending    TodoStatus = <span class="string">&quot;pending&quot;</span></span><br><span class="line">    TodoStatusInProgress TodoStatus = <span class="string">&quot;in_progress&quot;</span></span><br><span class="line">    TodoStatusDone       TodoStatus = <span class="string">&quot;done&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> TodoStatus <span class="type">string</span></span><br></pre></td></tr></table></figure></div>

<p>この TodoStatus のように「特定の複数個の値」に対しては、 <strong>排他的に</strong> 処理を書きたい場面がよくあります。<br>しかし Go の場合、 <code>switch</code> の case 文で網羅できていない値があってもコンパイラがエラーを出してくれません。そのため、排他性を担保するには目視で確認したり、テストで担保したりする必要があります。<br>また、 enum 値を後から追加した場合の対応漏れも、同様にコンパイラでは検知できません。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-hhte3j-5" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-hhte3j-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NextLabel</span><span class="params">(s TodoStatus)</span></span> <span class="type">string</span> &#123;</span><br><span class="line">    <span class="keyword">switch</span> s &#123;</span><br><span class="line">    <span class="keyword">case</span> TodoStatusPending:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;未着手&quot;</span></span><br><span class="line">    <span class="keyword">case</span> TodoStatusInProgress:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;進行中&quot;</span></span><br><span class="line">    <span class="comment">// TodoStatusDone を書き忘れてもコンパイルは通る</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>Rust であれば、処理していない enum がある場合 (&#x3D; 排他的な処理ができていない場合) はコンパイルエラーになってくれます。<br>また、後から enum を追加してその enum の処理を忘れていた場合であっても、コンパイラがエラーを吐いてくれるため、処理の書き忘れも防ぐことができます。</p>
<div class="code-block"><figure class="highlight rs"><input type="checkbox" id="code-wrap-hhte3j-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-hhte3j-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">TodoStatus</span> &#123;</span><br><span class="line">    Pending,</span><br><span class="line">    InProgress,</span><br><span class="line">    Done,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">next_label</span>(s: TodoStatus) <span class="punctuation">-&gt;</span> &amp;<span class="symbol">&#x27;static</span> <span class="type">str</span> &#123;</span><br><span class="line">    <span class="keyword">match</span> s &#123;</span><br><span class="line">        TodoStatus::Pending =&gt; <span class="string">&quot;未着手&quot;</span>,</span><br><span class="line">        TodoStatus::InProgress =&gt; <span class="string">&quot;進行中&quot;</span>,</span><br><span class="line">        <span class="comment">// TodoStatus::Done を書き忘れると `non-exhaustive patterns` でコンパイルエラー</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h3 id="Option-型-nil-が存在しないこと-nil-アクセスをコンパイラが防いでくれる">Option 型 + nil が存在しないこと: nil アクセスをコンパイラが防いでくれる</h3><p>Go で「値が存在するか不明」を表現するイディオムの1つに、ポインタを返し nil で「無し」を示す書き方があります。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-hhte3j-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-hhte3j-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> Hoge <span class="keyword">struct</span> &#123;</span><br><span class="line">    fieldA <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ReturnHogeOrNil</span><span class="params">()</span></span> *Hoge &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="comment">// nil チェックを挟む</span></span><br><span class="line">    hoge := ReturnHogeOrNil()</span><br><span class="line">    <span class="keyword">if</span> hoge == <span class="literal">nil</span> &#123;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;hoge is nil&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// nil でないことを確認しないと、 hoge に対する操作を行えない</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;fieldA:%d&quot;</span>, hoge.fieldA)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>この nil チェックが正しく行われているかは、コンパイル時にはチェックできず、ランタイム時の panic の大きな要因となります。<br>「nil チェック問題」は gopher を悩ませる大きな悩みのひとつでしょう。</p>
<p>一方、同じことを Rust で書くと、以下のように <code>Option&lt;Hoge&gt;</code> を返す形になります。</p>
<div class="code-block"><figure class="highlight rs"><input type="checkbox" id="code-wrap-hhte3j-8" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-hhte3j-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Hoge</span> &#123;</span><br><span class="line">    field_a: <span class="type">i32</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">return_hoge_or_none</span>() <span class="punctuation">-&gt;</span> <span class="type">Option</span>&lt;Hoge&gt; &#123;</span><br><span class="line">    <span class="literal">None</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">hoge</span> = <span class="title function_ invoke__">return_hoge_or_none</span>();</span><br><span class="line">    <span class="comment">// hoge は Option&lt;Hoge&gt; 型なので、いきなり hoge.field_a と書くとコンパイルエラーになる</span></span><br><span class="line">    <span class="comment">// println!(&quot;field_a: &#123;&#125;&quot;, hoge.field_a);</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// None / Some を場合分けしないと、中身に触れられない</span></span><br><span class="line">    <span class="keyword">match</span> hoge &#123;</span><br><span class="line">        <span class="title function_ invoke__">Some</span>(hoge) =&gt; <span class="built_in">println!</span>(<span class="string">&quot;field_a: &#123;&#125;&quot;</span>, hoge.field_a),</span><br><span class="line">        <span class="literal">None</span>       =&gt; <span class="built_in">println!</span>(<span class="string">&quot;hoge is none&quot;</span>),</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p><code>return_hoge_or_none</code> の戻り値は <code>Hoge</code> ではなく <code>Option&lt;Hoge&gt;</code> なので、 そのまま <code>hoge.field_a</code> のようにアクセスしようとするとコンパイラがエラーを出してくれます。<br><code>match</code> (もしくは <code>if let</code>) で <code>None</code> と <code>Some</code> を書き分けないと中身に触れられない作りになっているため、 「nil チェック忘れ」 という Go でよく起こりがちな状況が、 言語仕様の段階で防がれています。</p>
<h2 id="簡単な-Web-アプリケーションを作ってみた感想">簡単な Web アプリケーションを作ってみた感想</h2><p>普段 Web アプリケーション開発に携わっている身としては、やはり Rust で Web アプリケーションを作る際の体験がよいのかは気になるところです。</p>
<p>こちらを確認するために、 Go と Rust で同じような挙動をする簡単な Web アプリケーションを作り、比較してみました。<br>以下に作成したコードを公開しています。<br>https://github.com/yy-at-here/compare-go-rust</p>
<blockquote>
<p>※ Go と Rust 自体の比較というよりは、 選定したライブラリ&#x2F;クレート の比較になってしまっているかもしれません。ご了承ください。</p>
</blockquote>
<h3 id="Web-フレームワーク-axum-について">Web フレームワーク (axum) について</h3><p>Web フレームワークとして axum クレートを使用してみました。<br>ルーティング処理の書き方は思ったより難しくなく、ハンドラや extractor の設計のおかげで、所有権やライフタイムをほぼ意識せずに書くことができました。</p>
<p>DDD 的なディレクトリ構造への分割も、割と素直に表現できました。</p>
<h3 id="ORM-について">ORM について</h3><p>ORM の使い心地に関しては、Go（というか bob）に軍配が上がると感じました。以下、その点について述べます。</p>
<p>以下の 2 つの ORM を比較してみました。<br>どちらも、 DB からモデルを生成できる ORM です。</p>
<ul>
<li>Go : bob</li>
<li>Rust : SeaORM</li>
</ul>
<p>以下のようなモデルで、 特定の group にひもづく users と todos をすべて取得するような処理を考えます。</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">[Group]  ──|──&lt;  [User] ──|──&lt; [Todo]</span><br></pre></td></tr></table></figure>

<p>bob では、 eager load を連鎖的かつ直感的に書くことができます。<br>また、関連テーブルとのリレーションも生成されるモデルに含まれるため、わざわざ構造体を別途用意しなくとも Group モデルから Users や Todos を辿れる形になっています。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-hhte3j-9" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-hhte3j-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(q *groupQuery)</span></span> FindByID(ctx context.Context, id <span class="type">int64</span>) (*dbmodels.Group, <span class="type">error</span>) &#123;</span><br><span class="line">    dbGroup, err := dbmodels.Groups.Query(</span><br><span class="line">        dbmodels.SelectWhere.Groups.ID.EQ(id),</span><br><span class="line">        dbmodels.SelectThenLoad.Group.Users(</span><br><span class="line">            dbmodels.SelectThenLoad.User.Todos(),</span><br><span class="line">        ),</span><br><span class="line">    ).One(ctx, q.exec)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> dbGroup, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>また、以下のように簡単に関連テーブルにアクセスできます。 (ただし、その代償として nil check の面倒くささは課題としてあります)</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    group, _ := q.FindByID(ctx, <span class="number">1</span>)</span><br><span class="line">    <span class="keyword">if</span> group == <span class="literal">nil</span> || group.R == <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> _, user := <span class="keyword">range</span> group.R.Users &#123;</span><br><span class="line">        <span class="keyword">if</span> user.R == <span class="literal">nil</span> &#123;</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> _, todo := <span class="keyword">range</span> user.R.Todos &#123;</span><br><span class="line">            <span class="comment">// todo に関する処理</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>SeaORM から生成されるモデルでは、 group.R.Users のように group のフィールドに users が紐づいてくれません。<br>また、連鎖的な eager load も書けません。<br>group に紐づいた形で users も todos も持つためには、以下のように <code>Option&lt;(groups::Model, Vec&lt;(users::Model, Vec&lt;todos::Model&gt;)&gt;)&gt;</code> という長い型で持つ必要が出てきます。</p>
<div class="code-block"><figure class="highlight rs"><input type="checkbox" id="code-wrap-hhte3j-10" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-hhte3j-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">fn</span> <span class="title function_">find_by_group_id</span>(&amp;<span class="keyword">self</span>, group_id: <span class="type">i32</span>) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;</span><br><span class="line">    <span class="type">Option</span>&lt;(groups::Model, <span class="type">Vec</span>&lt;(users::Model, <span class="type">Vec</span>&lt;todos::Model&gt;)&gt;)&gt;,</span><br><span class="line">    crate::errors::AppError,</span><br><span class="line">&gt; &#123;</span><br><span class="line">    <span class="comment">// まず group を取得</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">Some</span>(group) = groups::Entity::<span class="title function_ invoke__">find_by_id</span>(group_id <span class="keyword">as</span> <span class="type">i64</span>).<span class="title function_ invoke__">one</span>(&amp;<span class="keyword">self</span>.db).<span class="keyword">await</span>? <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_ invoke__">Ok</span>(<span class="literal">None</span>);</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 次に、 group に紐づく users と todos を取得</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">users</span> = group.<span class="title function_ invoke__">find_related</span>(users::Entity).<span class="title function_ invoke__">all</span>(&amp;<span class="keyword">self</span>.db).<span class="keyword">await</span>?;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">todos_per_user</span>: <span class="type">Vec</span>&lt;<span class="type">Vec</span>&lt;todos::Model&gt;&gt; = users.<span class="title function_ invoke__">load_many</span>(todos::Entity, &amp;<span class="keyword">self</span>.db).<span class="keyword">await</span>?;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// users と todos をひとつのタプル列にまとめる</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">users_with_todos</span>: <span class="type">Vec</span>&lt;(users::Model, <span class="type">Vec</span>&lt;todos::Model&gt;)&gt; =</span><br><span class="line">        users.<span class="title function_ invoke__">into_iter</span>().<span class="title function_ invoke__">zip</span>(todos_per_user).<span class="title function_ invoke__">collect</span>();</span><br><span class="line"></span><br><span class="line">    <span class="title function_ invoke__">Ok</span>(<span class="title function_ invoke__">Some</span>((group, users_with_todos)))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 呼び出し側では、 Option と長いタプル型をネストで分解する必要がある</span></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">let</span> <span class="variable">Some</span>((group, users_with_todos)) = repo.<span class="title function_ invoke__">find_by_group_id</span>(<span class="number">1</span>).<span class="keyword">await</span>? &#123;</span><br><span class="line">    <span class="comment">// group: groups::Model</span></span><br><span class="line">    <span class="comment">// users_with_todos: Vec&lt;(users::Model, Vec&lt;todos::Model&gt;)&gt;</span></span><br><span class="line">    <span class="keyword">for</span> (user, todos) <span class="keyword">in</span> users_with_todos &#123;</span><br><span class="line">        <span class="comment">// user に紐づく todos に対する処理</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>上記の例はテーブルが 3 つだからまだそこまで複雑なコードにはなっていませんが、より複雑なリレーションを扱いたい場合は、コードの記述量や自前で定義する struct が多くなり、かなり大変なのではないか、という印象を受けました。<br>また、 <code>group.R.Users[i].R.Todos</code> と連鎖的にたどることができないのも、個人的にはつらく感じました。</p>
<p>ORM ごとの設計思想の違いも関わってくるところだとは思うので、どちらが良い&#x2F;悪いという話ではないかなと思いますが、個人的には bob の方が使いやすいなと感じました。<br>連鎖的な eager load や <code>group.R.Users[i].R.Todos</code> のような辿り方を Rust で実装するのがどれくらい難しいのか、色々調べてみたのですがよく分からなかったため、知見ある方いたら教えていただけますと大変嬉しいです。</p>
<h2 id="おわりに">おわりに</h2><p>Rust を書くと、「今この値は生きているのか」「この値は今誰が所有しているのか」「この値は不変にすべきか可変でよいか」といったことを嫌でも意識せざるを得ず、結果としてメモリの安全性を担保するためのマインドセットが醸成されるような感触を受けました。<br>Rust は確かに難しく、Rust 特有の概念 (所有権、ライフタイム、スマートポインタ など…) を理解する必要があります。ただ、この難しさの壁については、 AI に色々質問できるようになったことで、ある程度緩和されているな、と学習しながら感じていました。<br>Go で感じていた課題が Rust によって色々と解決されており、より堅牢性や速度が求められる場面では、 Rust を選択するのもありと感じました。</p>
<p>一方、大規模な Web アプリケーションに向いているのかは判断しきれませんでした。大規模になってくるとビルドが非常に遅い、みたいな話もあり、ローカルでのテストや CI の実行時間が長くなってしまうのは地味に懸念ポイントのひとつかな、とも思います。<br>このあたり知見がある方がもしいらっしゃいましたら、コメントなどで教えていただけると嬉しいです。</p>
<p>ともあれ、 Rust は「所有権」や「ライフタイム」などの独自の概念によって、堅牢性・メモリ安全性と速度を両立させている、非常に面白い言語でした。<br>また機会を見つけて触ってみようと思います。</p>
]]></content>
    <summary type="html">業務では主に Go を書いているのですが、「Rust は面白い」という話は方々から聞いており、自分もどこかで学んでおきたい、という気持ちがありました。そこで、せっかくの入門祭りということで、これを機に Rust に入門してみることにしました。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="ORM" scheme="https://future-architect.github.io/tags/ORM/"/>
    <category term="Rust" scheme="https://future-architect.github.io/tags/Rust/"/>
    <category term="入門" scheme="https://future-architect.github.io/tags/%E5%85%A5%E9%96%80/"/>
  </entry>
  <entry>
    <title>Java製のSalesforce Apexパーサーをブラウザで動かす</title>
    <link href="https://future-architect.github.io/articles/20260225a/"/>
    <id>https://future-architect.github.io/articles/20260225a/</id>
    <published>2026-02-24T15:00:00.000Z</published>
    <updated>2026-02-24T15:00:00.000Z</updated>
    <author><name>二村暢之</name></author>
    <content type="html"><![CDATA[<p>コアテクノロジーグループの二村です。</p>
<p>普段は膨大なドキュメントやソースコードを解析して、ファクトベースでお客様のシステム移行計画策定や、保守改善を支援するコンサルティング業務を行っています。また、そのためのマネージドサービスを開発しています。</p>
<p>先日、仕事の一貫でSalesforce Apex<sup id="fnref:apex">1</sup>というJava5に似た構文を持つ言語のパーサーをJava(jdkのみ)で実装したので、それをブラウザ上で動かすという実験をしてみました。JavaからJavaScriptとWebAssemblyの両方にコンパイルして、パフォーマンスを比較できる形で検証しています。</p>
<p>この記事では、その開発過程で遭遇した技術的な課題と、その解決策について詳しく解説します。特に、TeaVMを使ったJavaからWebAssemblyへのコンパイルの実装上の工夫や、ブラウザ環境でのメモリ管理、文字列のinteropなど、実際に開発してみないとわからない細かいポイントも紹介します。</p>
<h2 id="プロジェクトの概要">プロジェクトの概要</h2><img fetchpriority="high" src="/images/2026/20260225a/TeaVMによるclassファイルのコンパイル.png" alt="TeaVMによるclassファイルのコンパイル" width="1200" height="655">

<p><strong>目標</strong>: 自作Java製Apexパーサーをブラウザで動かし、ApexソースコードのAST<sup id="fnref:ast">2</sup>をインタラクティブに可視化すること。</p>
<p><strong>技術スタック</strong>:</p>
<ul>
<li><strong>TeaVM 0.13.0</strong>: JavaバイトコードをJavaScript&#x2F;WebAssemblyにコンパイルするツール<ul>
<li>2026年2月頭時点で最新の0.13.0を使用</li>
<li>Mavenのpom.xmlには、以下の依存関係（dependency）を記述しました<ul>
<li>org.teavm:teavm-core：TeaVMのコアライブラリ</li>
<li>org.teavm:teavm-classlib：Javaの標準APIをTeaVMで動作するように実装したクラスライブラリ</li>
<li>org.teavm:teavm-jso：JavaコードからJavaScriptのオブジェクトや関数を操作するためのアノテーションやユーティリティクラスを提供</li>
<li>org.teavm:teavm-jso-apis：TeaVMのJSO（JavaScript Object）ライブラリで使用されるAPIの定義を含むライブラリ</li>
<li>org.teavm:teavm-maven-plugin：MavenビルドプロセスにTeaVMのコンパイルステップを組み込むためのプラグイン</li>
</ul>
</li>
</ul>
</li>
<li><strong>Java 17</strong><ul>
<li>Salesforce Apexパーサー自体はJava8互換で実装していますが、TeaVMの最新機能を活用するためにJava17でビルドしています</li>
</ul>
</li>
<li><strong>Maven 3.3.9</strong>: ビルドツール</li>
<li><strong>Jetty 9.4</strong>: 開発用Webサーバー</li>
<li><strong>Vanilla JS</strong>: フロントエンド（フレームワークなし）</li>
</ul>
<p><strong>成果物</strong>:</p>
<p>2バージョンのカーソル位置連動ハイライト機能付きAST Viewer</p>
<ul>
<li>JavaScript版</li>
<li>WebAssembly版</li>
</ul>
<p>ユーザーがApexソースコードでカーソル位置を動かすと対応するASTノードをハイライトする機能を実装したのでその様子を動画で紹介します。</p>
<p><strong>JavaScript版デモ</strong><br><img src="/images/2026/20260225a/js_demo.avif" alt="Apex Parser Playground JS Demo" width="1200" height="690" loading="lazy"></p>
<p><strong>WebAssembly版デモ</strong><br><img src="/images/2026/20260225a/wasm_demo.avif" alt="Apex Parser Playground WASM Demo" width="1200" height="690" loading="lazy"></p>
<h2 id="なぜTeaVMを選んだのか">なぜTeaVMを選んだのか</h2><p>Javaコードをブラウザで動かす選択肢はいくつかあります。</p>
<p>以下の表で主要な選択肢を比較しました：</p>
<p>※バイナリサイズはコードに依存するためあくまで目安です。実際のサイズはプロジェクトによって大きく異なります。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>項目</th>
<th>TeaVM (JS)</th>
<th>TeaVM (WASM)</th>
<th>CheerpJ</th>
<th>GraalVM Wasm</th>
<th>GWT&#x2F;J2CL</th>
</tr>
</thead>
<tbody><tr>
<td><strong>アプローチ</strong></td>
<td>.classバイトコード→JS AOT</td>
<td>.classバイトコード→Wasm AOT</td>
<td>JVMエミュレーション（ブラウザ内JVM）</td>
<td>Java→Wasm AOT</td>
<td>Java→JS source-to-source(トランスパイル)</td>
</tr>
<tr>
<td><strong>Javaバージョン</strong></td>
<td>Java 8+</td>
<td>Java 8+</td>
<td>Java 8&#x2F;11</td>
<td>Java 17+</td>
<td>Java 8-11</td>
</tr>
<tr>
<td><strong>既存jar対応</strong></td>
<td>◎（一部リフレクション制限）</td>
<td>◎（一部リフレクション制限）</td>
<td>◎（完全互換だが遅い）</td>
<td>△（GraalVM Native Image制約）</td>
<td>△（未対応API多数）</td>
</tr>
<tr>
<td><strong>バイナリサイズ</strong></td>
<td>1-2MB（最適化後）</td>
<td>2-4MB（ランタイム含む）</td>
<td>5-10MB（JVM含む）</td>
<td>3-10MB</td>
<td>1-3MB</td>
</tr>
<tr>
<td><strong>parse性能</strong></td>
<td>速い</td>
<td>より速い</td>
<td>遅いはず（JVMエミュレーション）</td>
<td>速いはず</td>
<td>速い？</td>
</tr>
<tr>
<td><strong>String&#x2F;JS相互運用</strong></td>
<td>◎（<code>@JSBody</code>で直接）</td>
<td>△（手動UTF-16変換）</td>
<td>△（JNI風API）</td>
<td>△（Wasm Interface Types待ち）</td>
<td>◎（Java↔JS透過的）</td>
</tr>
<tr>
<td><strong>DOM&#x2F;ブラウザAPI</strong></td>
<td>◎（<code>teavm-jso</code>）</td>
<td>○（限定的）</td>
<td>○（JNI風）</td>
<td>△（外部JS必要）</td>
<td>◎（JSNI&#x2F;JsInterop）</td>
</tr>
<tr>
<td><strong>成熟度&#x2F;コミュニティ</strong></td>
<td>○（中規模、活発）</td>
<td>△（発展途上）</td>
<td>△（商用中心）</td>
<td>△（実験的）</td>
<td>○（大規模だが停滞気味）</td>
</tr>
<tr>
<td><strong>パーサー向き</strong></td>
<td>◎</td>
<td>◎</td>
<td>△</td>
<td>○</td>
<td>○</td>
</tr>
</tbody></table></div>
<p>TeaVMを選んだ理由は以下の通りです：</p>
<ul>
<li><strong>WebAssemblyのサポート</strong><ul>
<li>ver0.9.0でWASMターゲットが安定化</li>
<li>ver0.13.0でJava25までのclassファイルをサポートしており、<code>Thread.start</code>や<code>Thread.sleep</code>などのThread系メソッドもサポート</li>
<li><code>java.lang</code>, <code>java.util(OptionalやStreamを含む)</code>, <code>java.io</code> などの主要なクラスはエミュレーションされる※<code>java.nio.file</code> や <code>java.net(Socketなど)</code>、<code>java.awt/swing</code> など、ブラウザ環境にそぐわないAPIは利用不可</li>
<li>リフレクションはメタプログラムによる静的解析でサポートされますが、動的なクラスローディングやリフレクションは制限される</li>
<li>利用する自作parserは別プロジェクトとなっており今回の作成したplaygroundプロジェクトからはdependencyとして利用します。TeaVMはバイナリを解析してJavaScript&#x2F;WebAssemblyに変換するため、既存のjarをそのまま利用できる点も大きなメリット</li>
</ul>
</li>
<li><strong>Mavenとの統合</strong><ul>
<li>既存のビルドフローに簡単に組み込める</li>
<li>Mavenは単に好みですがGradleもサポート</li>
</ul>
</li>
<li><strong>サイズ効率</strong><ul>
<li>最適化オプションが充実</li>
</ul>
</li>
<li><strong>アクティブな開発</strong><ul>
<li>2026年現在でも活発</li>
</ul>
</li>
<li><strong>自作パーサー向き</strong><ul>
<li>文字列操作ロジック中心</li>
<li>jdkのみで特殊なクラスを利用をしていない</li>
</ul>
</li>
</ul>
<p><strong>CheerpJ</strong> は完全なJVM互換性があるようですが、ブラウザ内JVMエミュレーションによるオーバーヘッドが大きく、パーサーのような処理では遅くなります。既存jarを無理やりブラウザで動かすには便利ですが、パフォーマンスが重要な場合は注意が必要です。</p>
<p><strong>GraalVM Wasm</strong> は高性能。ただし、Native Imageを作成してからWebAssemblyに変換するという2段階プロセスが必要。別途検証してみたいなと思っています。</p>
<p><strong>GWT&#x2F;J2CL</strong> はJavaScript変換が成熟していますが、Java8ベースで新機能対応が遅く、J2CLはgoogle社の内部ツールでドキュメント不足です。</p>
<h2 id="アーキテクチャ設計：デュアルターゲット戦略">アーキテクチャ設計：デュアルターゲット戦略</h2><p>当初はWebAssembly版のみを考えていましたが、ブラウザ互換性とパフォーマンスの両立を考え、<strong>JavaScriptとWebAssemblyの両方</strong>を同時に生成する戦略に変更しました。</p>
<h3 id="ビルドフロー">ビルドフロー</h3><ul>
<li>JsMain&#x2F;WasmMainはそれぞれJavaScript版とWebAssembly版のエントリーポイントとなる(ソースコード文字列を引数とする)mainクラスです。</li>
</ul>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line">mvn clean package</span><br><span class="line">  ↓</span><br><span class="line">[Maven Compiler Plugin]</span><br><span class="line">  ↓ Java → .class</span><br><span class="line">[TeaVM Plugin: compile-js]</span><br><span class="line">  ↓ JsMain.java → classes.js</span><br><span class="line">  （519 classes、4170 methods）</span><br><span class="line"></span><br><span class="line">[TeaVM Plugin: compile-wasm]</span><br><span class="line">  ↓ WasmMain.java → classes.wasm</span><br><span class="line">  （561 classes、4515 methods）</span><br><span class="line">  ↓</span><br><span class="line">[WAR Packaging]</span><br><span class="line">  → 配備可能なアプリケーション</span><br></pre></td></tr></table></figure>

<h3 id="エントリーポイントの分離">エントリーポイントの分離</h3><p>最初の躓きポイントがここでした。<br><strong>JavaScriptとWebAssemblyで別々のMainクラスを使う</strong> 必要があったのです。</p>
<p><strong>なぜ分離の必要があったのか？</strong></p>
<p>TeaVMではJavaScriptとWebAssemblyでそれぞれ公開用のエントリーポイントをアノテーションベースで実装する必要があります。</p>
<p>TeaVMでは、以下のようにコンパイルターゲットによって異なるアノテーションを使用します：</p>
<ul>
<li><strong>JavaScript</strong>: <code>@JSBody</code>を使ってJavaScriptコードを直接埋め込み</li>
<li><strong>WebAssembly</strong>: <code>@Export</code>でWASM関数としてエクスポート</li>
</ul>
<p>これらのアノテーションは同じクラス内で併用できないため、別々のMainクラスを用意することになりました。<br>当初は1つのMainクラスで<code>@JSBody</code>と<code>@Export</code>を併用しようとしましたが、コンパイル時に競合が発生しました。TeaVMのJavaScriptターゲットとWebAssemblyターゲットでは、interop<sup id="fnref:interop">3</sup>の仕組みが根本的に異なるためです。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-42i29q-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-42i29q-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// JavaScript用エントリーポイント</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JsMain</span> &#123;</span><br><span class="line">    <span class="comment">// TeaVMではJNIのnative構文を流用し、@JSBodyでJavaScript実装を埋め込む（JavaScriptブリッジ用）</span></span><br><span class="line">    <span class="meta">@JSBody(params = &#123; &quot;fn&quot; &#125;, script =</span></span><br><span class="line"><span class="meta">        &quot;window.apexParser = &#123; parseApex: function(source) &#123; &quot; +</span></span><br><span class="line"><span class="meta">        &quot;return fn.parse(source); &#125; &#125;; &quot; +</span></span><br><span class="line"><span class="meta">        &quot;if (window.onParserReady) window.onParserReady();&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title function_">registerParser</span><span class="params">(ParseFunction fn)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        registerParser((source) -&gt; parseApex(source));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> String <span class="title function_">parseApex</span><span class="params">(String source)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">SourceInfo</span> <span class="variable">sourceInfo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SourceInfo</span>(source);</span><br><span class="line">            <span class="type">ApexLexer</span> <span class="variable">lexer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ApexLexer</span>(sourceInfo);</span><br><span class="line">            <span class="type">ApexParser</span> <span class="variable">parser</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ApexParser</span>(lexer);</span><br><span class="line">            <span class="type">CompilationUnitNode</span> <span class="variable">ast</span> <span class="operator">=</span> parser.parseCompilationUnit();</span><br><span class="line"></span><br><span class="line">            <span class="type">ASTToJsonVisitor</span> <span class="variable">visitor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ASTToJsonVisitor</span>();</span><br><span class="line">            ast.accept(visitor);</span><br><span class="line">            <span class="keyword">return</span> visitor.toJsonString();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;&#123;\&quot;error\&quot;:\&quot;&quot;</span> + escapeJson(e.getMessage()) + <span class="string">&quot;\&quot;&#125;&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// WebAssembly用エントリーポイント</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WasmMain</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// ダミー呼び出しでDead Code Elimination回避</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            parseApex(<span class="string">&quot;public class Test &#123;&#125;&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="comment">// 無視</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * WebAssemblyでエクスポートする関数。</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@Export</span>(name = &quot;parseApex&quot;) により、parseApexという名前でJSから呼び出せる関数としてエクスポートされる。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Export(name = &quot;parseApex&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">parseApex</span><span class="params">(String apexSource)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">SourceInfo</span> <span class="variable">sourceInfo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SourceInfo</span>(apexSource);</span><br><span class="line">            <span class="type">ApexLexer</span> <span class="variable">lexer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ApexLexer</span>(sourceInfo);</span><br><span class="line">            <span class="type">ApexParser</span> <span class="variable">parser</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ApexParser</span>(lexer);</span><br><span class="line">            <span class="type">CompilationUnitNode</span> <span class="variable">ast</span> <span class="operator">=</span> parser.parseCompilationUnit();</span><br><span class="line"></span><br><span class="line">            <span class="type">ASTToJsonVisitor</span> <span class="variable">visitor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ASTToJsonVisitor</span>();</span><br><span class="line">            ast.accept(visitor);</span><br><span class="line">            <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> visitor.toJsonString();</span><br><span class="line"></span><br><span class="line">            System.gc();  <span class="comment">// メモリリーク対策</span></span><br><span class="line">            <span class="keyword">return</span> result;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;&#123;\&quot;error\&quot;:\&quot;&quot;</span> + escapeJson(e.getMessage()) + <span class="string">&quot;\&quot;&#125;&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h2 id="はまりポイント-その1：Dead-Code-Elimination">はまりポイント その1：Dead Code Elimination</h2><p>テストビルドが通り、Webサーバーを起動し、いざブラウザで動かしてみると…</p>
<div class="code-block"><figure class="highlight text"><input type="checkbox" id="code-wrap-42i29q-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-42i29q-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">Error: parseApex function not found in WASM exports</span><br></pre></td></tr></table></figure></div>

<p>デバッグ用のログを仕込んで調べると、<code>ApexLexer</code>や<code>ApexParser</code>といった肝心のクラスが<strong>存在しない</strong>ことが判明しました。</p>
<h3 id="原因：強力すぎるデッドコード除去（Dead-Code-Elimination）">原因：強力すぎるデッドコード除去（Dead Code Elimination）</h3><p>TeaVMは使われていないコードを検出して積極的に除去します。WasmMainのmain()メソッドが空だったため、<code>parseApex()</code>メソッドは「呼ばれることがない」と判断され、依存する全クラスが除外されていたのです。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ これだとparserクラスが除外される</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="comment">// 空っぽ</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ ダミー呼び出しでクラス参照を保持</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        parseApex(<span class="string">&quot;public class Test &#123;&#125;&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="comment">// 無視</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>このダミー呼び出しにより、コンパイラに「このメソッドは使われる」と認識させて解決しました。</p>
<h2 id="はまりポイント-その2：メモリリーク">はまりポイント その2：メモリリーク</h2><p>Dead Code Elimination問題を解決し、パースが動き始めたのも束の間、次の問題が待っていました。</p>
<p>連続でパースを実行すると、30回目くらいで突然エラーが発生します：</p>
<figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">RuntimeError: memory access out of bounds</span><br></pre></td></tr></table></figure>

<h3 id="原因：WebAssemblyのリニアメモリ制限">原因：WebAssemblyのリニアメモリ制限</h3><p>WebAssemblyは「リニアメモリ」という固定サイズのメモリ領域を使います。TeaVMのデフォルトヒープサイズは<strong>16MB</strong>と小さめ。</p>
<p>ASTノードやトークンを大量に生成するパース処理を繰り返すと、GCが実行されずメモリが枯渇していたようです。</p>
<h3 id="解決策：2つのアプローチ">解決策：2つのアプローチ</h3><p><strong>1. ヒープサイズの拡大</strong></p>
<p>pom.xmlでWASMのヒープサイズを明示的に指定しました：</p>
<div class="code-block"><figure class="highlight xml"><input type="checkbox" id="code-wrap-42i29q-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-42i29q-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">id</span>&gt;</span>compile-wasm<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">minHeapSize</span>&gt;</span>32<span class="tag">&lt;/<span class="name">minHeapSize</span>&gt;</span>  <span class="comment">&lt;!-- 32MB --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">maxHeapSize</span>&gt;</span>128<span class="tag">&lt;/<span class="name">maxHeapSize</span>&gt;</span> <span class="comment">&lt;!-- 128MB --&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br></pre></td></tr></table></figure></div>

<p><strong>2. 明示的なGC呼び出し</strong></p>
<p>パース処理の最後に<code>System.gc()</code>を追加：</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-42i29q-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-42i29q-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta">@Export(name = &quot;parseApex&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">parseApex</span><span class="params">(String apexSource)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// パース処理</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> visitor.toJsonString();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 明示的にGCを要求</span></span><br><span class="line">        System.gc();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&#123;\&quot;error\&quot;:\&quot;&quot;</span> + escapeJson(e.getMessage()) + <span class="string">&quot;\&quot;&#125;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>通常のJavaでは<code>System.gc()</code>は「お願い」でしかありませんが、TeaVMのWASM環境では比較的確実に動作します。</p>
<p>この2つの対策により、<strong>100回以上の連続パースでも安定動作</strong>するようになりました。</p>
<h2 id="はまりポイント-その3：文字列のinterop">はまりポイント その3：文字列のinterop</h2><p>WebAssemblyは文字列を直接扱えません。JavaScriptとWASM間で文字列を受け渡すには、<strong>手動でUTF-16変換</strong>が必要です。</p>
<h3 id="JavaScript側の変換関数">JavaScript側の変換関数</h3><div class="code-block"><figure class="highlight javascript"><input type="checkbox" id="code-wrap-42i29q-5" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-42i29q-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">jsStringToJava</span>(<span class="params">str</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!teavm) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// TeaVMのヒープに文字列用領域を確保</span></span><br><span class="line">    <span class="keyword">let</span> javaString = teavm.<span class="title function_">allocateString</span>(str.<span class="property">length</span>);</span><br><span class="line">    <span class="keyword">if</span> (javaString === <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> dataArrayPtr = teavm.<span class="title function_">stringData</span>(javaString);</span><br><span class="line">    <span class="keyword">let</span> dataAddress = teavm.<span class="title function_">objectArrayData</span>(dataArrayPtr);</span><br><span class="line">    <span class="keyword">let</span> dataView = <span class="keyword">new</span> <span class="title class_">Uint16Array</span>(teavm.<span class="property">memory</span>.<span class="property">buffer</span>, dataAddress, str.<span class="property">length</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// UTF-16配列として書き込み</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; str.<span class="property">length</span>; ++i) &#123;</span><br><span class="line">        dataView[i] = str.<span class="title function_">charCodeAt</span>(i);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> javaString;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">javaStringToJs</span>(<span class="params">javaString</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!teavm || javaString === <span class="number">0</span>) <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> dataArrayPtr = teavm.<span class="title function_">stringData</span>(javaString);</span><br><span class="line">    <span class="keyword">let</span> length = teavm.<span class="title function_">arrayLength</span>(dataArrayPtr);</span><br><span class="line">    <span class="keyword">let</span> dataAddress = teavm.<span class="title function_">objectArrayData</span>(dataArrayPtr);</span><br><span class="line">    <span class="keyword">let</span> dataView = <span class="keyword">new</span> <span class="title class_">Uint16Array</span>(teavm.<span class="property">memory</span>.<span class="property">buffer</span>, dataAddress, length);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> result = <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; length; ++i) &#123;</span><br><span class="line">        result += <span class="title class_">String</span>.<span class="title function_">fromCharCode</span>(dataView[i]);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>これはgithub copilotが書いてくれたとはいえ、正直<strong>面倒</strong>です。JavaScript版では<code>@JSBody</code>で直接文字列を受け渡せるため、このような変換は不要です。なお<code>@JSBody</code>よりもっとシンプルに使える @JSExport というアノテーションもあります。</p>
<p>パフォーマンスとのトレードオフですね。</p>
<h2 id="AST可視化：JSON形式への移行">AST可視化：JSON形式への移行</h2><p>初期バージョンでは、パーサープロジェクト側のテストで利用していたので下記のようなテキストベースのAST出力を直接JavaScriptに返していました。</p>
<figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">CompilationUnit [1:1-10:2]</span><br><span class="line">  ClassDeclaration: HelloWorld [1:1-10:2]</span><br><span class="line">    MethodDeclaration: void sayHello [2:5-4:6]</span><br><span class="line">      ...</span><br></pre></td></tr></table></figure>

<p>しかし、これをJavaScript側でパースして位置情報を抽出するのは困難だった(当たり前)ため <strong>JSON形式</strong>に移行することにしました。</p>
<h3 id="ASTToJsonVisitor実装">ASTToJsonVisitor実装</h3><p>ダブルディスパッチのVisitorパターンで全ASTノードを走査し、JSON文字列を構築します：</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-42i29q-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-42i29q-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// これはイメージです。実際には全ノードタイプに対応するvisitメソッドが必要になります。</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ASTToJsonVisitor</span> <span class="keyword">implements</span> <span class="title class_">ASTVisitor</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">StringBuilder</span> <span class="variable">json</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">visit</span><span class="params">(CompilationUnitNode node)</span> &#123;</span><br><span class="line">        json.append(<span class="string">&quot;&#123;&quot;</span>);</span><br><span class="line">        appendProperty(<span class="string">&quot;type&quot;</span>, <span class="string">&quot;CompilationUnitNode&quot;</span>);  <span class="comment">// js側でdispatch用のtypeプロパティ</span></span><br><span class="line">        appendLocation(node.getLocation());</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!node.getClassDeclarations().isEmpty()) &#123;</span><br><span class="line">            json.append(<span class="string">&quot;,\&quot;classDeclarations\&quot;:[&quot;</span>);</span><br><span class="line">            <span class="type">boolean</span> <span class="variable">first</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">            <span class="keyword">for</span> (ClassDeclarationNode cls : node.getClassDeclarations()) &#123;</span><br><span class="line">                <span class="keyword">if</span> (!first) json.append(<span class="string">&quot;,&quot;</span>);</span><br><span class="line">                cls.accept(<span class="built_in">this</span>);</span><br><span class="line">                first = <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            json.append(<span class="string">&quot;]&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        json.append(<span class="string">&quot;&#125;&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">appendLocation</span><span class="params">(Location loc)</span> &#123;</span><br><span class="line">        json.append(<span class="string">&quot;,\&quot;location\&quot;:&#123;&quot;</span>);</span><br><span class="line">        json.append(<span class="string">&quot;\&quot;startLine\&quot;:&quot;</span>).append(loc.getStartLine());</span><br><span class="line">        json.append(<span class="string">&quot;,\&quot;endLine\&quot;:&quot;</span>).append(loc.getEndLine());</span><br><span class="line">        json.append(<span class="string">&quot;,\&quot;startColumn\&quot;:&quot;</span>).append(loc.getStartColumn());</span><br><span class="line">        json.append(<span class="string">&quot;,\&quot;endColumn\&quot;:&quot;</span>).append(loc.getEndColumn());</span><br><span class="line">        json.append(<span class="string">&quot;,\&quot;startPosition\&quot;:&quot;</span>).append(loc.getStartPosition());</span><br><span class="line">        json.append(<span class="string">&quot;,\&quot;endPosition\&quot;:&quot;</span>).append(loc.getEndPosition());</span><br><span class="line">        json.append(<span class="string">&quot;&#125;&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p><strong>なぜJSONライブラリを使わないのか？</strong></p>
<p>実は、TeaVMでは一般的なJSONライブラリ（Jackson、Gson等）がリフレクションが問題になりそのまま動かないことがあります。</p>
<p>単にJSON文字列が作れればいいだけなので、 <code>StringBuilder</code>で手動構築するので十分で、コンパイル後のコードサイズも小さく抑えられます。</p>
<p>ここもgithub copilotが書いてくれるので手間はあまりかかりませんでした。</p>
<h2 id="パフォーマンス比較：JavaScriptとWebAssembly">パフォーマンス比較：JavaScriptとWebAssembly</h2><p>両方のビルドが動くようになったので、ベンチマークを取りました。</p>
<h3 id="Apexソース（900行弱のクラス）">Apexソース（900行弱のクラス）</h3><p>WebAssembly版はJavaScript版の約2倍速い結果が出ました。<br>とはいえ、JavaScript版も実用上は十分な速度であり、体感としては誤差の範囲内と言えます。<br>どちらもJITコンパイルされるため、初回は遅いですが、2回目以降は安定して速くなります。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>指標</th>
<th>JavaScript</th>
<th>WebAssembly</th>
<th>比率</th>
</tr>
</thead>
<tbody><tr>
<td>Parse Time（平均）</td>
<td>100ms</td>
<td>50ms</td>
<td><strong>約2.0x</strong></td>
</tr>
<tr>
<td>Parse Time（最速）</td>
<td>79ms</td>
<td>35ms</td>
<td><strong>約2.0x</strong></td>
</tr>
<tr>
<td>Classes Compiled</td>
<td>519</td>
<td>561</td>
<td>+42</td>
</tr>
<tr>
<td>Methods Compiled</td>
<td>4170</td>
<td>4515</td>
<td>+345</td>
</tr>
<tr>
<td>File Size</td>
<td>781KB※</td>
<td>2.2MB</td>
<td><strong>約3.0x</strong></td>
</tr>
<tr>
<td>Initial Load</td>
<td>速い</td>
<td>やや遅い</td>
<td>WASM instantiation分遅い</td>
</tr>
</tbody></table></div>
<p>※ <strong>TeaVM 0.10.2</strong> では812KBでした。 <strong>TeaVM 0.13.0</strong> までに最適化がより進んだ可能性があります。</p>
<h3 id="考察">考察</h3><p><strong>WebAssemblyの利点</strong>:</p>
<ul>
<li>実行速度が<strong>2倍高速</strong></li>
<li>大規模解析で真価を発揮する可能性</li>
<li>連続処理でも安定</li>
</ul>
<p><strong>WebAssemblyの欠点</strong>:</p>
<ul>
<li>ファイルサイズが3倍</li>
<li>初回読み込みがやや遅い</li>
<li>文字列変換のオーバーヘッド</li>
<li>ブラウザサポートがChrome 88+, Firefox 89+, Edge 88+に限定</li>
</ul>
<p><strong>JavaScript版の利点</strong>:</p>
<ul>
<li>ファイルサイズが小さい</li>
<li>ブラウザ互換性が高い</li>
<li>文字列操作が容易</li>
<li>それでも十分な速度</li>
</ul>
<p><strong>結論</strong>: 小規模パースならJavaScript版で十分。大規模な解析や連続処理、リアルタイム性が求められる場面ではWebAssembly版が有利。</p>
<h2 id="ビルドオプションの詳細解説">ビルドオプションの詳細解説</h2><p>TeaVMのMavenプラグインには多くの設定オプションがあります。pom.xmlに入れた設定を説明します。</p>
<h3 id="JavaScript版の設定">JavaScript版の設定</h3><div class="code-block"><figure class="highlight xml"><input type="checkbox" id="code-wrap-42i29q-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-42i29q-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">id</span>&gt;</span>compile-js<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">goal</span>&gt;</span>compile<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">phase</span>&gt;</span>process-classes<span class="tag">&lt;/<span class="name">phase</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- エントリーポイント --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">mainClass</span>&gt;</span>jp.co.future.tools.apex.playground.JsMain<span class="tag">&lt;/<span class="name">mainClass</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 出力先（開発用にsrc/main/webappへ直接出力） --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">targetDirectory</span>&gt;</span>$&#123;project.basedir&#125;/src/main/webapp<span class="tag">&lt;/<span class="name">targetDirectory</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- ターゲット種別 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">targetType</span>&gt;</span>JAVASCRIPT<span class="tag">&lt;/<span class="name">targetType</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">targetFileName</span>&gt;</span>classes.js<span class="tag">&lt;/<span class="name">targetFileName</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 最適化レベル：SIMPLE, ADVANCED, FULL --&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- FULL = Dead Code Elimination + インライン化 + 定数畳み込み --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">optimizationLevel</span>&gt;</span>FULL<span class="tag">&lt;/<span class="name">optimizationLevel</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 変数名短縮化（サイズ削減） --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">minifying</span>&gt;</span>true<span class="tag">&lt;/<span class="name">minifying</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- デバッグ情報を含めない（本番用） --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">debugInformationGenerated</span>&gt;</span>false<span class="tag">&lt;/<span class="name">debugInformationGenerated</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- エラーがあっても処理継続 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">stopOnErrors</span>&gt;</span>false<span class="tag">&lt;/<span class="name">stopOnErrors</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- ファイナライザの厳格チェックを無効化 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">strictFinalization</span>&gt;</span>false<span class="tag">&lt;/<span class="name">strictFinalization</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br></pre></td></tr></table></figure></div>

<p><strong>主要オプション解説</strong>:</p>
<ul>
<li><p><strong>optimizationLevel</strong>:</p>
<ul>
<li><code>SIMPLE</code>: 基本的な最適化のみ（開発用）</li>
<li><code>ADVANCED</code>: インライン化や定数畳み込み</li>
<li><code>FULL</code>: Dead Code Eliminationを含む完全最適化（本番用）<ul>
<li>今回の実験だと<code>SIMPLE/minifyなし</code> に比べて、サイズが1&#x2F;3になりました</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>minifying</strong>: 変数名を短縮（例：<code>parseApexSourceCode</code> → <code>a</code>）。可読性は下がるがサイズが30-40%削減される</p>
</li>
<li><p><strong>debugInformationGenerated</strong>: ソースマップとスタックトレース情報を生成。開発時は<code>true</code>、本番は<code>false</code></p>
</li>
</ul>
<h3 id="WebAssembly版の設定">WebAssembly版の設定</h3><div class="code-block"><figure class="highlight xml"><input type="checkbox" id="code-wrap-42i29q-8" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-42i29q-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">id</span>&gt;</span>compile-wasm<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">goal</span>&gt;</span>compile<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">phase</span>&gt;</span>process-classes<span class="tag">&lt;/<span class="name">phase</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- エントリーポイント --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">mainClass</span>&gt;</span>jp.co.future.tools.apex.playground.WasmMain<span class="tag">&lt;/<span class="name">mainClass</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- ターゲット種別 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">targetType</span>&gt;</span>WEBASSEMBLY<span class="tag">&lt;/<span class="name">targetType</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 出力先（src/main/webappへ出力） --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">targetDirectory</span>&gt;</span>$&#123;project.basedir&#125;/src/main/webapp<span class="tag">&lt;/<span class="name">targetDirectory</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 出力ファイル名 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">targetFileName</span>&gt;</span>classes.wasm<span class="tag">&lt;/<span class="name">targetFileName</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 最適化レベル --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">optimizationLevel</span>&gt;</span>FULL<span class="tag">&lt;/<span class="name">optimizationLevel</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- *** メモリ設定（重要） *** --&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 初期ヒープサイズ（MB） --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">minHeapSize</span>&gt;</span>32<span class="tag">&lt;/<span class="name">minHeapSize</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 最大ヒープサイズ（MB） --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">maxHeapSize</span>&gt;</span>128<span class="tag">&lt;/<span class="name">maxHeapSize</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">debugInformationGenerated</span>&gt;</span>false<span class="tag">&lt;/<span class="name">debugInformationGenerated</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">stopOnErrors</span>&gt;</span>false<span class="tag">&lt;/<span class="name">stopOnErrors</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">strictFinalization</span>&gt;</span>false<span class="tag">&lt;/<span class="name">strictFinalization</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br></pre></td></tr></table></figure></div>

<p><strong>WASM固有オプション解説</strong>:</p>
<ul>
<li><p><strong>minHeapSize&#x2F;maxHeapSize</strong>: WebAssemblyのリニアメモリサイズ。デフォルト16MBは小さすぎるため、パーサー用途では32-128MBを推奨</p>
</li>
<li><p><strong>heapDump</strong>: （オプション）メモリリーク調査用。<code>true</code>にするとヒープダンプを出力</p>
</li>
<li><p><strong>wasmVersion</strong>: （オプション）WebAssemblyのバージョン。デフォルトは<code>V_0x1</code>（MVP）</p>
<ul>
<li>指定したのですがエラーになりました</li>
<li>TeaVMでは指定ができないようです</li>
</ul>
</li>
</ul>
<h3 id="開発時のTips">開発時のTips</h3><p><strong>開発中は最適化を弱める</strong>:</p>
<div class="code-block"><figure class="highlight xml"><input type="checkbox" id="code-wrap-42i29q-9" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-42i29q-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">optimizationLevel</span>&gt;</span>SIMPLE<span class="tag">&lt;/<span class="name">optimizationLevel</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">minifying</span>&gt;</span>false<span class="tag">&lt;/<span class="name">minifying</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">debugInformationGenerated</span>&gt;</span>true<span class="tag">&lt;/<span class="name">debugInformationGenerated</span>&gt;</span></span><br></pre></td></tr></table></figure></div>

<p>これにより：</p>
<ul>
<li>ビルド時間が短縮（FULL: 45秒 → SIMPLE: 20秒）</li>
<li>エラーメッセージが読みやすい</li>
<li>ブラウザDevToolsでのデバッグが容易</li>
</ul>
<p><strong>本番環境では完全最適化</strong>:</p>
<div class="code-block"><figure class="highlight xml"><input type="checkbox" id="code-wrap-42i29q-10" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-42i29q-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">optimizationLevel</span>&gt;</span>FULL<span class="tag">&lt;/<span class="name">optimizationLevel</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">minifying</span>&gt;</span>true<span class="tag">&lt;/<span class="name">minifying</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">debugInformationGenerated</span>&gt;</span>false<span class="tag">&lt;/<span class="name">debugInformationGenerated</span>&gt;</span></span><br></pre></td></tr></table></figure></div>

<p>サイズが30-50%削減され、起動時間も改善します。</p>
<h2 id="学んだこと">学んだこと</h2><h3 id="1-WebAssemblyは万能ではない">1. WebAssemblyは万能ではない</h3><p>WebAssemblyは確かに高速ですが、ファイルサイズやブラウザ互換性のトレードオフがあります。Salesforce Apexのようなパースでは「2倍速い」という結果でしたが、JavaScript版でも十分実用的です。<br>WebAssemblyの場合はfetchでモジュールを読み込む必要があるためWebサーバが必要ですが、JavaScript版ならローカルで直接開いても動きます。用途に応じて使い分けるのが良さそうです。</p>
<h3 id="2-Dead-Code-Eliminationは両刃の剣">2. Dead Code Eliminationは両刃の剣</h3><p>最適化は重要ですが、意図しないクラス除去が起きると原因特定が困難です。main()での明示的な参照は、一種の「アンカー」として機能します。</p>
<h3 id="3-メモリ管理はWASMの課題">3. メモリ管理はWASMの課題</h3><p>WebAssemblyのリニアメモリは有限です。特にGC言語（Java, Kotlin等）をコンパイルする場合、ヒープサイズの調整と明示的なGC呼び出しが重要になります。</p>
<h3 id="4-文字列変換のオーバーヘッド">4. 文字列変換のオーバーヘッド</h3><p>WebAssemblyのinteropで文字列を扱うのは思ったより面倒です。TeaVMは比較的良好なAPIを提供していますが、それでも手動変換が必要です。将来的にはWebAssembly Interface Types※で改善される予定です。</p>
<p>※WebAssembly Interface Typesとは、WebAssemblyモジュールがJavaScriptや他の言語とより自然にデータをやり取りできるようにするための提案です。これが実装されれば、文字列や複雑なデータ構造の変換が大幅に簡素化されるでしょう。</p>
<h3 id="5-JSON形式の威力">5. JSON形式の威力</h3><p>言うまでもありませんが、JavaScriptとのやりとりで構造化データをやり取りする場合、JSON形式は強力です。手動で文字列を構築するのは面倒ですが、JSON形式になってしまえばJavaScript側で自由にトラバースできます。</p>
<h3 id="6-TeaVMのスタブ機能">6. TeaVMのスタブ機能</h3><p>TeaVMの<code>teavm-classlib</code>には、ブラウザ環境では実際に動作しないAPIのスタブが含まれています。例えば、TeaVM&#x2F;WebAssemblyの制約により <code>java.nio.file</code>パッケージのようなファイルI&#x2F;O操作をサポートしていませんが、クラス参照やメソッドシグネチャは問題なくコンパイルできます。</p>
<p>自作パーサーの<code>SourceInfo</code>クラスでは<code>java.nio.file.Path</code>を使用していましたが、TeaVMの<code>teavm-classlib</code>に基本的なスタブ（<code>TPath</code>、<code>TPaths</code>など）が含まれているため、特別な対応なしにコンパイルできました。</p>
<p>もし<code>teavm-classlib</code>にないクラスを参照する必要がある場合は、以下のようなスタブを作成できます：</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-42i29q-11" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-42i29q-11" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// stubs/org/teavm/classlib/com/example/MyClass.java</span></span><br><span class="line"><span class="keyword">package</span> org.teavm.classlib.com.example;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyClass</span> &#123;</span><br><span class="line">    <span class="comment">// ブラウザでは実際に呼ばれないメソッドのスタブ実装</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">someMethod</span><span class="params">(String arg)</span> &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>(<span class="string">&quot;Not supported in browser&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>このスタブを<code>org.teavm.classlib</code>パッケージ配下に配置することで、TeaVMのクラスローダーが優先的に読み込み、コンパイルエラーを回避できます。実際にそのメソッドが実行されなければ、例外は発生しません。</p>
<h2 id="今後の展望">今後の展望</h2><p>GraalVM Wasmも試してみたいと思っています。GraalVMはJavaをネイティブコードにコンパイルする機能があり、そこからさらにWebAssemblyに変換できます。性能面では非常に期待できる一方で、ビルドフローが複雑になる可能性があります。</p>
<h2 id="まとめ">まとめ</h2><p>JavaからWebAssemblyへのコンパイルは、思ったより実用的でした。TeaVMは強力なツールですが、いくつかの気をつける点があることを学びました。どれも回避策は存在するので、適切に対処すれば安定したブラウザアプリケーションを構築できます。</p>
<ul>
<li><strong>Dead Code Elimination</strong>: main()でクラス参照を保持</li>
<li><strong>メモリ管理</strong>: ヒープサイズ調整とGC呼び出し</li>
<li><strong>文字列変換</strong>: 手動でUTF-16変換</li>
<li><strong>デュアルターゲット</strong>: JavaScriptとWASMで別々のMainクラス</li>
<li><strong>スタブの利用に関する注意</strong>: TeaVMではブラウザ環境で動作しないAPI（例: <code>java.nio.file</code>）に対してスタブを提供しており、これを適切に扱う必要があります。</li>
</ul>
<p>今回やったような実験もAIエージェントによりだいぶ楽になりました。コードの自動生成やリファクタリングに大活躍でした。</p>
<h2 id="さいごに">さいごに</h2><p>コアテクノロジーグループの私のチームでは下記のような記事を書いています。</p>
<ul>
<li>Pure Rustで生まれ変わったPostgreSQL公式構文準拠SQLフォーマッター「uroborosql-fmt」をリリース🎉 | フューチャー技術ブログ<ul>
<li>Pure Rustでpostgresqlの構文準拠のcst-parserを実装しています</li>
</ul>
</li>
<li>ANTLRを業務で活用した話 | フューチャー技術ブログ<ul>
<li>ANTLRのようなパーサージェネレーターを利用することもあります</li>
</ul>
</li>
<li>Pyright を LSP サーバとした自作 LSP クライアント（実装編） | フューチャー技術ブログ<ul>
<li>PyrightというPythonの型チェッカーを言語解析エンジンとして利用した事例</li>
</ul>
</li>
</ul>
<p>コアテクノロジーグループでは、現在チームメンバーを募集しています。言語処理やコンパイラー技術が好きな方、グラフ理論、グラフ可視化、アルゴリズム好きな方、ソフトウェア工学の知識を使って仕事をしたい方を歓迎します。</p>
<p>興味がある方はお気軽に技術ブログTwitterや会社採用HPへ、連絡をお待ちしております。</p>
<p>https://www.future.co.jp/recruit/</p>
<h2 id="注釈">注釈</h2><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style:none; padding-left: 0;"><li id="fn:apex"><span style="vertical-align: top; padding-right: 10px;">1.</span><span style="vertical-align: top;">ApexとはSalesforceのプラットフォーム専用言語です。Java5に似た構文を持ちながら、SOQL/SOSLといった独自のオブジェクトクエリ機能を持つ言語です。公式サイト：Apex とは? | Apex 開発者ガイド | Salesforce Developers</span> ↩</li><li id="fn:ast"><span style="vertical-align: top; padding-right: 10px;">2.</span><span style="vertical-align: top;">AST（Abstract Syntax Tree）とは、ソースコードの構文構造を表す木構造のデータです。パーサーはソースコードをASTに変換し、バイナリ生成やコード解析などの後続処理で利用されます。</span> ↩</li><li id="fn:interop"><span style="vertical-align: top; padding-right: 10px;">3.</span><span style="vertical-align: top;">interopとは、JavaコードとJavaScript/WASMコードが相互に呼び出し合うための仕組みです。</span> ↩</li></ol></div></div>]]></content>
    <summary type="html">Salesforce ApexというJava5に似た構文を持つ言語のパーサーをJavaで実装したので、それをブラウザ上で動かすという実験をしてみました。JavaからJavaScriptとWebAssemblyの両方にコンパイルして、パフォーマンスを比較できる形で検証しています。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Java" scheme="https://future-architect.github.io/tags/Java/"/>
    <category term="Salesforce" scheme="https://future-architect.github.io/tags/Salesforce/"/>
    <category term="WebAssembly" scheme="https://future-architect.github.io/tags/WebAssembly/"/>
  </entry>
  <entry>
    <title>非同期設計ガイドラインを公開しました</title>
    <link href="https://future-architect.github.io/articles/20260220a/"/>
    <id>https://future-architect.github.io/articles/20260220a/</id>
    <published>2026-02-19T15:00:00.000Z</published>
    <updated>2026-02-19T15:00:00.000Z</updated>
    <author><name>亀井隆徳</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260220a/top.jpg" alt="" width="1024" height="559">

<h2 id="はじめに">はじめに</h2><p>こんにちは。TIG（Technology Innovation Group）の亀井です。</p>
<p>フューチャー社内の有志メンバーで 非同期設計ガイドライン を作成し、公開しました！</p>
<p>本記事では、ガイドライン策定の背景や、ガイドラインで取り上げている設計のポイントをピックアップしてご紹介します。</p>
<h2 id="本ガイドライン策定の背景">本ガイドライン策定の背景</h2><p>かつて非同期処理といえば、専門的なメッセージングミドルウェアを必要とする、一部のミッションクリティカルなシステムで採用される特別な技術でした。フューチャーでも独自のミドルウェアフレームワークを構築して、大量データをリアルタイムで処理するような仕組みを数々の工夫を凝らして実装してきました。</p>
<p>一方で、昨今ではAWS SQSなどのクラウドネイティブなサービスの登場により、応答時間の長い処理のオフロードなどを目的に非同期処理を取り入れることは珍しくなくなりました。</p>
<p>しかし、非同期特有の難しさは依然として存在しており、「処理のトレース」「デバッグ」「リラン」が困難である点や、データストアにまたがる場合のデータ整合性担保、障害発生時のリカバリなど、同期処理にはない複雑さが伴います。</p>
<p>本ガイドラインでは、非同期導入のメリットを享受しつつ、これらの「本質的な難しさ」を回避、または適切に管理するための実務的な設計論点と指針を提供することを目的としています。</p>
<h2 id="本ガイドラインの対象読者">本ガイドラインの対象読者</h2><p>本ガイドラインは、バックエンドシステムにおいて、Web APIやバッチ処理からのトリガーによる非同期メッセージング（キュー）を用いた処理を設計・実装するエンジニア・アーキテクトを対象としています。</p>
<h2 id="ガイドラインの内容紹介">ガイドラインの内容紹介</h2><p>ガイドラインは多岐にわたる論点をカバーしていますが、ここでは特に議論になりやすいポイントをいくつか抜粋して紹介します。</p>
<h3 id="1-非同期化の判断基準と使い分け">1. 非同期化の判断基準と使い分け</h3><p>「なんとなく非同期にする」のではなく、明確な判断基準を持つことを推奨しています。ガイドラインでは以下の3つの観点を提示しています。</p>
<ul>
<li><strong>処理時間と応答性:</strong> ファイル生成など、同期的に待つとUXが低下する場合</li>
<li><strong>負荷の平準化:</strong> 突発的な大量アクセスなどバースト的な負荷を直接受けずに平準化したい場合</li>
<li><strong>レジリエンスの向上:</strong> 外部システムへの依存を切り離し、メイン処理の継続性を高めたい場合</li>
</ul>
<p>一方で、これらに該当しない場合やリソースの増強や運用で回避できるケースでは、システム全体の複雑性を下げるために「同期処理」を選択することも合理的であり、安易な非同期化を避けることも推奨としています。</p>
<h3 id="2-論理構成方針">2. 論理構成方針</h3><p>スケーラビリティや障害分離の観点から、<strong>「1業務タスク &#x3D; 1キュー &#x3D; 1コンシューマー」</strong> という構成を推奨しています。</p>
<p>複数の異なる業務（例: メール送信と決済処理）を1つのキューに混在させると、障害時の切り分けが困難になったり、業務ごとの優先度に応じた流量制御（スロットリング）ができなくなるため業務タスクの単位でキューおよびコンシューマーを分けることを推奨としています。<br>管理するリソース数は増えますが、AWS Lambdaにおける「予約済み同時実行数」などのリソース制限を有効活用するためにも、シンプルな構成にしておく方がよいでしょう。</p>
<h3 id="3-データ整合性とトランザクションアウトボックスパターン">3. データ整合性とトランザクションアウトボックスパターン</h3><p>非同期処理で頻出する課題に「DB更新とメッセージ送信の整合性」があります。DBのコミットには成功したがメッセージ送信に失敗する（あるいはその逆）といった事態を防ぐため、<strong>トランザクションアウトボックスパターン</strong> の採用是非についても触れています。</p>
<blockquote>
<p>トランザクションアウトボックスパターン</p>
<ol>
<li>各コンシューマーは自身の担う業務ロジック処理と、処理完了を示すステータス更新を1トランザクションで実施する。</li>
<li>後続コンシューマーへのメッセージ連携は、別のトランザクションで行う。</li>
</ol>
<p>これにより、1→2の順序性の担保と、2単体でのリトライが可能となる。</p>
</blockquote>
<p>ガイドラインでは、ロストメッセージやファントムメッセージの対策として、ステータス管理テーブルを用いたアプローチや、プロデューサー側での登録とコンシューマー側でのチェックによる整合性担保について比較・解説しています。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>用語</th>
<th>状態</th>
<th>発生する問題</th>
</tr>
</thead>
<tbody><tr>
<td>ロストメッセージ</td>
<td>DBコミット成功 &#x2F; キュー送信失敗</td>
<td>メッセージが消失し、後続処理が動かない</td>
</tr>
<tr>
<td>ファントムメッセージ</td>
<td>キュー送信成功 &#x2F; DBコミット失敗</td>
<td>存在しないデータを処理しようとしてエラーになる</td>
</tr>
</tbody></table></div>
<h3 id="4-順序保証とFIFOキュー">4. 順序保証とFIFOキュー</h3><p>厳密な順序保証が必要な場合、SQS FIFOキューなどの利用が検討されますが、スループットの低下や「Head-of-Line (HOL) ブロッキング」のリスクが伴います。</p>
<p>本ガイドラインでは、<strong>「可能な限り順序制御を必要としない設計（冪等性の確保や、メッセージの独立性）」</strong> を目指すことを推奨しています。その上で、どうしても順序保証が必要な場合のグルーピング戦略（MessageGroupIdの設計）についても言及しています。</p>
<h3 id="5-エラーハンドリングとDLQ">5. エラーハンドリングとDLQ</h3><p>処理失敗時のメッセージ退避先であるDead Letter Queue(DLQ)について、<strong>「二段構え」</strong> の構成を推奨しています。</p>
<ol>
<li><strong>アプリケーション制御:</strong> バリデーションエラーなど、既知のエラーはアプリが即時にDLQへ退避させる（ブロッキングを最小化するため）</li>
<li><strong>インフラ制御:</strong> クラッシュなど予期せぬエラーは、インフラ（SQSのmaxReceiveCountなど）の機能で救済する</li>
</ol>
<p>これにより、FIFOキュー利用時のブロッキング時間を最小化しつつ、予期せぬ障害時にもメッセージをロストしない堅牢性を確保します。</p>
<h2 id="まとめ">まとめ</h2><p>非同期設計ガイドラインは、現代の分散システム開発において避けては通れない「非同期処理」の設計判断を支援するために作成しました。</p>
<p>Webフロントエンド設計ガイドライン や バッチ設計ガイドライン と同様に、本ガイドラインも社内外のフィードバックを受けて継続的にアップデートしていく予定です。</p>
<p>GitHub上でのIssueやPRも大歓迎ですので、ぜひご覧ください。</p>
<ul>
<li><strong>GitHub</strong>: future-architect&#x2F;arch-guidelines</li>
</ul>
<h2 id="関連ガイドライン">関連ガイドライン</h2><ul>
<li>Webフロントエンド設計ガイドラインを公開しました</li>
<li>バッチ設計ガイドラインを公開しました</li>
<li>Web API設計ガイドラインを公開しました</li>
</ul>
]]></content>
    <summary type="html">フューチャー社内の有志メンバーで非同期設計ガイドライン を作成し、公開しました！</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="SQS" scheme="https://future-architect.github.io/tags/SQS/"/>
    <category term="ガイドライン" scheme="https://future-architect.github.io/tags/%E3%82%AC%E3%82%A4%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3/"/>
  </entry>
  <entry>
    <title>今なぜ「アルゴリズム」を学ぶのか？ —— Software Design2026年1月号への寄稿によせて</title>
    <link href="https://future-architect.github.io/articles/20260217a/"/>
    <id>https://future-architect.github.io/articles/20260217a/</id>
    <published>2026-02-16T15:00:00.000Z</published>
    <updated>2026-02-16T15:00:00.000Z</updated>
    <author><name>真野隼記</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260217a/pxl_20260121_025217424.jpg" alt="" width="1000" height="1333">

<p>同僚の澁川さん、松本さんと一緒に、Software Design 2026年1月号の第1特集「アルゴリズムはどこに効く？」にて、第3章「パフォーマンス問題の診断とアーキテクチャの再考」を寄稿しました。</p>
<ul>
<li>澁川さんの記事: https://future-architect.github.io/articles/20251218a/</li>
</ul>
<p>今回は記事の宣伝も兼ねて「なぜ今、現場のエンジニアがアルゴリズムやデータ構造を学ぶ必要があるのか？」というテーマで、執筆の裏側にある思いを紹介します。</p>
<h2 id="はじめに">はじめに</h2><p>正直に言うと、最初に編集部からオファーをいただいた時、引き受けるべきかどうか少し迷いました。</p>
<p>今の私の日常業務において、教科書に出てくるような「アルゴリズム」をバリバリ実装する機会は多くないためです。ソートや探索が必要なら標準ライブラリを使いますし、複雑なデータ処理もSQLやらクラウドのマネージドサービスに任せます。「個々のエンジニアがアルゴリズムを深く学ぶ必要性は下がっているのではないか？」という論調を見かけることがありますが、そういった人も多いという背景からでしょう。</p>
<p>しかし、考えを巡らせていくと、Web系のアプリケーションを開発する中では、選定眼はむしろ求められること、そのため存在自体を知っていることはプラスに働くなど押さえておくべきポイントはあります。また、システムレベルのアーキテクチャを見るうえで、アルゴリズムを学んだ経験がどのくらい活用できているか？について、言語化することにも興味を持ちました。このあたりのモチベーションで、今回の第3章を執筆しました。</p>
<p>ここでは執筆にあたり意識したコンセプトを3つ紹介します。</p>
<h2 id="コードとアーキテクチャのフラクタル性について">コードとアーキテクチャのフラクタル性について</h2><p>古典的なソートや探索などのアルゴリズムをコードレベルと呼ぶと、システムレベルのアーキテクチャは全く別物ではあるのですが、似たようなことに関心を持つことがあります。</p>
<ul>
<li>学生やジュニア時代、私は <code>for</code> ループの回数を気にし、計算量をどう減らすかよく思考をめぐらしていました。ループの外側で以下に処理しやすい構造を作って…など（懐かしい）</li>
<li>現在、サーバー間の通信回数（ラウンドトリップ）を気にし、Web API呼び出しやDBへのクエリ数をどう減らすかなどに関心が移りました</li>
</ul>
<p>これらはスケールが違うだけで、概念としては同じことをしているとも言えるでしょう。</p>
<ul>
<li><strong>コードレベル：</strong> 重い計算結果をメモリに保存して再利用する「メモ化」、ループの外で処理しやすいオブジェクトを作っておく</li>
<li><strong>システムレベル：</strong> DB負荷を下げるためにRedisやCDNを挟む「キャッシュ戦略」、ワークテーブルで処理しやすい構造を予め作っておく</li>
</ul>
<p>このように、システムレベルの設計は、コード上のロジックで考えていたことのフラクタル（自己相似）な構造として捉えることもできます。「アルゴリズムなんて実務で使わない」のではなく、様々な差異はあれど、形を変えて、よりマクロな視点で目の前に存在しているとも言えます。本誌ではいくつかアルゴリズムの焼き直しだと私が感じたシステム構成についても触れています。</p>
<h2 id="I-Oがある世界">I&#x2F;Oがある世界</h2><p>本特集の第1章（基礎）・第2章（CPU&#x2F;メモリ最適化）で語られる世界は、ある種、理論的で机上に近く美しい世界です。しかし、私が担当した第3章（I&#x2F;O・アーキテクチャ）の世界はもっと泥臭く、混沌としています。</p>
<p>実システム、特にWebサービスやモバイルアプリのバックエンドでは、以下のような「現実」と戦わなければなりません。</p>
<ul>
<li>通信のレイテンシ（遅延が大きい話）</li>
<li>ネットワークの瞬断とリトライ</li>
<li>データの整合性</li>
</ul>
<p>アルゴリズムもデータ準備など色々むずかしい点はありますが、システムレベルですと検証の準備・実行コストが非常に大きいです。負荷試験は純粋に、インフラ費用が高くそう何度も繰り返し行うことが予算上できない場合があります。</p>
<p>だからこそ、「とりあえず作ってみる」前に、設計段階でボトルネックを見抜く「審美眼」が問われます。ここで役立つのが、やはりアルゴリズムとデータ構造の知識です。本誌ではどういった考慮点があり、どういった順序でこれらを切り分けて対策していくか一段掘り下げて書いています。</p>
<h2 id="「勘」のヒット率を上げる原理原則">「勘」のヒット率を上げる原理原則</h2><p>性能問題が発生した場合、現場では、計測なしに「とりあえずここを直してみよう」「リソースを増やしてみよう」というアプローチが取られることもあります。</p>
<p>「推測するな、計測せよ」は好きな言葉ですが、こうした経験に基づく「勘」も否定できません。実際、熟練者の勘で即座に解決することもままあるからです。さほど重要な機能でなければそれでお茶を濁し、より業務効果が高いところに時間を投下するのも、状況によってはありでしょう。</p>
<p>しかし、この「勘」の精度を高めるものに、データ構造とアルゴリズム的な教養が求められます。</p>
<ul>
<li>「ここはキュー構造で処理されているから、ここが詰まるはずだ」</li>
<li>「B+木のインデックス構造を考えると、このデータ配置は不利だ」</li>
<li>「今のキーで振り分けるとキャッシュが上手く使われていないのではないか」</li>
</ul>
<p>システムが巨大化しても、使われている部品の挙動はアルゴリズムの原理原則に従います。どこでどういった計算とデータの参照やコピーが行われているかは、深く入って仮説を作ろうとすると、基本情報として知識ベースで求められます。本誌では知っておくと良さそうな概念をいくつか紹介しています。</p>
<h2 id="さいごに">さいごに</h2><p>ここまでで話したような考えをもとに、本誌を執筆しました。</p>
<p>今回の特集は、以下のような構成になっています。</p>
<ul>
<li><strong>第1章：</strong> アルゴリズムの基礎とトレードオフ（けんちょんさん）</li>
<li><strong>第2章：</strong> CPU&#x2F;メモリレベルの最適化と限界（渋川さん・松本さん）</li>
<li><strong>第3章：</strong> アーキテクチャレベルの診断と再考（私）</li>
</ul>
<p>本誌のチラ見せもされていますので良ければぜひ。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">「アルゴリズムはソフトウェアの開発現場で役に立つのか？」… pic.twitter.com/tJgK8AuYf0</p>&mdash; SoftwareDesign (@gihyosd) December 26, 2025</blockquote> 

<p>ミクロなコードの世界からマクロなシステムの世界まで、これらを通読することで、エンジニアとしての見る力が一段階上がるはずです。tsukommoさんも良いことを言ってくれています。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ポチった良かった。<br>けんちょんさんが素朴なアルゴリズムの使い所を紹介。<br>渋川さんが標準データ構造の強さと例外として特定処理に特化した構造の強さ紹介。<br>真野さんがとはいえボトルネックはI/O側なのでアーキ設計が先、ただし古典的なアルゴリズムを抑えていると設計を外さない。<br>と繋がった。</p>&mdash; ツカモ (@tsukammo) December 27, 2025</blockquote> 

<ul>
<li>「クラウド時代だからこそ、基礎を固めたい」</li>
<li>「コードの速さだけでなく、システムの強さを設計したい」</li>
</ul>
<p>そう考えるエンジニアの方々に、ぜひ第3章を含め、本誌を手に取っていただければ幸いです。</p>
<ul>
<li>https://amzn.asia/d/dZ8eIgy</li>
</ul>
]]></content>
    <summary type="html">同僚の澁川さん、松本さんと一緒に、Software Design 2026年1月号の第1特集「アルゴリズムはどこに効く？」にて、第3章「パフォーマンス問題の診断とアーキテクチャの再考」を寄稿しました。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="SoftwareDesign" scheme="https://future-architect.github.io/tags/SoftwareDesign/"/>
    <category term="アルゴリズム" scheme="https://future-architect.github.io/tags/%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0/"/>
    <category term="出版" scheme="https://future-architect.github.io/tags/%E5%87%BA%E7%89%88/"/>
  </entry>
  <entry>
    <title>Go 1.26で変わるcgoの高速化 〜ランタイム刷新がもたらす30%の高速化とその舞台裏〜</title>
    <link href="https://future-architect.github.io/articles/20260205a/"/>
    <id>https://future-architect.github.io/articles/20260205a/</id>
    <published>2026-02-04T15:00:00.000Z</published>
    <updated>2026-02-04T15:00:00.000Z</updated>
    <author><name>宮崎将太</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260205a/image.png" alt="" width="1200" height="434">

<p>Go1.26ブログ連載の7本目です。</p>
<h2 id="はじめに">はじめに</h2><p>テクノロジーイノベーショングループの宮崎です。</p>
<p>Go1.26でcgoが高速化され、約30％速くなったと公表がありました。</p>
<p>これまでなんとなくでcgoを捉えていたので、これを機会にそもそもの仕組みやどのようにオーバーヘッドが短縮されたのか調べてみました。</p>
<h2 id="そもそもcgoとは？">そもそもcgoとは？</h2><p>そもそもcgoとはGo言語とC言語の間の「橋渡し」を行うパッケージおよび機能のことで、GoのプログラムからC言語で書かれた関数を呼び出したり、逆にC言語からGoの関数を呼び出したりすることが可能になります。</p>
<p>cgoを利用してCのコードを呼び出すには、<code>import &quot;C&quot;</code>という特殊な行を追加し、その直上のコメント（プリアンブル）にCのコードを記述します。このようにすることでC.関数の形式でCのコードを呼び出すことができます。</p>
<p>Goの場合はランタイムが自動的にメモリ管理をしてくれますが、Cを呼び出す場合はメモリ解放を自力で行う必要があるなど、注意が必要です。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-vrv7vf-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-vrv7vf-1" title="コードの折り返しを切り替える"></label><figcaption><span>GoからCを呼び出す場合</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">#include &lt;stdio.h&gt;</span></span><br><span class="line"><span class="comment">#include &lt;stdlib.h&gt;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">void hello_c(const char* name) &#123;</span></span><br><span class="line"><span class="comment">    printf(&quot;Hello, %s\n&quot;, name);</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;C&quot;</span> <span class="comment">// import Cの直上にコメントを配置する必要がある</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;unsafe&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="comment">// Goの文字列をCの文字列型 (*C.char) に変換</span></span><br><span class="line">    name := C.CString(<span class="string">&quot;Go code&quot;</span>)</span><br><span class="line">    <span class="keyword">defer</span> C.free(unsafe.Pointer(name))</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Cの関数を呼び出す</span></span><br><span class="line">    C.hello_c(name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>また、CからGoを呼び出すこともできます。</p>
<figure class="highlight go"><figcaption><span>CからGoを呼び出す場合: Go側コード</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;C&quot;</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//export Sum</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Sum</span><span class="params">(a, b C.<span class="type">int</span>)</span></span> C.<span class="type">int</span> &#123;</span><br><span class="line">	<span class="keyword">return</span> a + b</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;&#125;</span><br></pre></td></tr></table></figure>

<div class="code-block"><figure class="highlight c"><input type="checkbox" id="code-wrap-vrv7vf-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-vrv7vf-2" title="コードの折り返しを切り替える"></label><figcaption><span>CからGoを呼び出す場合: C側コード</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;libsum.h&quot;</span> <span class="comment">// Goのビルド時に自動生成されるファイル</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> a = <span class="number">10</span>;</span><br><span class="line">    <span class="type">int</span> b = <span class="number">20</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Goで定義した関数を呼び出す</span></span><br><span class="line">    <span class="type">int</span> result = Sum(a, b);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;result: %d&quot;</span>, result);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>上記2ファイルを用意し、GoコードをCの共有ライブラリとしてビルド後、Cコードをコンパイルすることで実行バイナリが生成されます。</p>
<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-vrv7vf-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-vrv7vf-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># -buildmode=c-shared を指定することで、Cから読める形式になる</span></span><br><span class="line"><span class="comment"># libsum.so（本体）と libsum.h（C用の定義）が生成される</span></span><br><span class="line">go build -o libsum.so -buildmode=c-shared main.go</span><br><span class="line"></span><br><span class="line"><span class="comment"># Cコードのコンパイル</span></span><br><span class="line">gcc -o main main.c ./libsum.so</span><br><span class="line"></span><br><span class="line"><span class="comment"># 実行</span></span><br><span class="line">./main</span><br></pre></td></tr></table></figure></div>

<h2 id="何がオーバーヘッドなのか？-境界超えのコスト">何がオーバーヘッドなのか？ 境界超えのコスト</h2><p>このように、C言語資産を簡単に活用できる土台がcgoによって整っており、一見便利に思えますが、多くのエンジニアを悩ませてきた種でもあります。</p>
<p>それがcgoによるネイティブ連携のオーバーヘッドです。C言語の資産を活用しようとするたびに、GoランタイムとCの実行環境を往復する境界越えでオーバーヘッドが発生し、「cgoは遅いからGoのみで再実装すべき」という意見もありました。</p>
<p>Go 1.26はこの意見を過去のものにする画期的なリリースです。</p>
<p>ランタイムの抜本的な刷新により、cgo呼び出しに伴うベースラインのオーバーヘッドが平均約30％削減されました。特定の環境ではさらに顕著で、Apple M1（ARM64）では33.4%、AMD EPYCでは17.99%もの高速化を叩き出しています。</p>
<h2 id="高速化の革新：-Psyscall状態の廃止">高速化の革新：<code>_Psyscall</code>状態の廃止</h2><p>Go 1.26における高速化の核心は、Goのスケジューリングモデル（G-M-Pモデル）の心臓部にあたる、プロセッサ（P）の状態管理の再設計にあります。</p>
<h3 id="G-M-Pモデルとは？">G-M-Pモデルとは？</h3><p>GoではOSから提供されるスレッドを直接扱うのではなく、より軽量なゴルーチンを効率良く管理するためにG-M-Pモデルというスケジューリングメカニズムを採用しています。</p>
<p>G・M・Pはそれぞれ以下のコンポーネントの頭文字です。</p>
<ul>
<li>G (Goroutine)<br>ゴルーチンそのものです。実行される関数やスタック情報、状態（待機中、実行中など）を保持しています。OSのスレッドに比べてメモリ消費が非常に少なく（数KB程度）、大量に生成できます</li>
<li>M (Machine &#x2F; OS Thread)<br>OSのスレッドです。実際にCPU上で計算する実体です。Gを実行するには、必ずこのMが必要です</li>
<li>P (Processor)<br>論理プロセッサです。「GをMに割り当てるための権利（リソース）」と考えてください。通常、マシンのCPUコア数と同じ数が設定されます</li>
</ul>
<p>昔のGo（初期）には「P」がなく、共通のグローバルなキューをすべてのMが奪い合っていました。これだと、スレッドが増えるほどロック競合が発生し、パフォーマンスが落ちるという弱点がありました。</p>
<p>そこで導入されたのが P（ローカルランキュー） です。</p>
<p>GMPの連携フローは以下になります。</p>
<ol>
<li>P が自分専用の「実行待ちGのリスト（Local Queue）」を持ちます</li>
<li>M は P を1つ捕まえて、その中にある G を順番に実行します</li>
<li>共通のグローバルキューを見に行く頻度が減るため、高速に処理を回せます</li>
</ol>
<p>このモデルでは以下の様に処理が偏ったときのリカバリー機能によって全体を効率化しています。</p>
<ul>
<li>Work Stealing（奪取）<br>あるMが自分の担当するPのGをすべて使い切って暇になったとき、他のPが持っているGの半分を盗んできて実行します。これにより、特定のコアだけ暇になるのを防ぎます</li>
<li>Hand-off（引き継ぎ）<br>実行中のGがシステムコール（入出力待ちなど）でブロックされた場合、Mも一緒に止まってしまいます。その時、Pは別のM（空いているスレッド）を探して、残りのGたちを引き継がせます</li>
</ul>
<p>要するに、ゴルーチンを成り立たせるコアロジックです。</p>
<h3 id="スケジューラの事務手続きのボトルネック">スケジューラの事務手続きのボトルネック</h3><p>G-M-Pモデルでは、通常 P は _Prunning（実行中）などの状態をとりますが、ゴルーチン（G）がシステムコールを呼び出すと、ランタイムは P の状態を<code>_Psyscall</code>に書き換えていました。</p>
<p>これはシステムコール中にPを他のスレッドが奪いやすくするための「目印」とすること目的でした。</p>
<p>これまでのGo（1.25以前）では、ゴルーチンがcgoを介してCコードを実行する際、担当するスレッド（M）は保持しているプロセッサ（P）の状態をわざわざ<code>_Psyscall</code>に変更していました。これはいわば「空港で手荷物がない乗客にも、わざわざ預け入れカウンターへの立ち寄りを強制する」ような、煩雑な手続きでした。この状態遷移には重いアトミック操作（CAS: Compare-And-Swap）が伴い、高並列環境で競合を引き起こす大きな要因となっていました。</p>
<h3 id="ゴルーチン中心の監視への移行">ゴルーチン中心の監視への移行</h3><p>Go 1.26では、このスケジューラの安全網であった _Psyscall 状態が廃止されました。</p>
<p>Pの状態をいちいち書き換えるのではなく、そのPの上で動いている ゴルーチンのステータス（_Gsyscall） を直接監視する仕組みに移行しました。これにより、短時間のcgo呼び出しであれば、MはPを手放す準備をすることなく保持し続けられることになります。システムモニター（sysmon）の挙動も刷新され、不要なPの奪取が抑制されることで、命令パスが劇的に短縮されました。</p>
<h2 id="cgo高速化によるメリット">cgo高速化によるメリット</h2><p>このオーバーヘッド削減は、特に「マイクロ秒未満の軽量なC関数」を頻繁に叩くライブラリにおいて、圧倒的なパワーを発揮します。</p>
<h3 id="go-sqlite3-へのインパクト">go-sqlite3 へのインパクト</h3><p>代表例は go-sqlite3 です。</p>
<p>SQLiteのドライバーはC言語で作られており、各言語のドライバーはC言語の純正ドライバーを呼び出す構成になっています。これまではcgoのオーバーヘッド問題により、<code>modernc.org/sqlite</code>のようにピュアなGo言語に置き換えられたライブラリを使用することもありましたが、今回のアップデートでgo-sqlite3を使用も考えられるようになりました。</p>
<p>SQLiteのクエリ実行は、SQLの準備・ステップ実行・カラム値の取得など非常に小さなcgo呼び出しの連鎖で成り立っており、 従来の「100nsのC処理 ＋ 50nsのオーバーヘッド ＝ 150ns」という構成が、Go 1.26では「100ns ＋ 35ns ＝ 135ns」に短縮されます。</p>
<p>この全体で約10％の効率向上は、大量のレコードをスキャンするバッチ処理において無視できない累積的利益をもたらします。</p>
<h3 id="重い処理（OpenCVなど）における真の価値">重い処理（OpenCVなど）における真の価値</h3><p>一方、OpenCV（GoCV）を用いた画像解析や機械学習の推論のように、C側でミリ秒単位の時間を要する処理では、ナノ秒単位の短縮は微々たるものです。</p>
<p>しかし、ここでの真の恩恵は速度ではなくスケジューリングの安定性です。</p>
<p>_Psyscall の廃止によって、sysmonによる不必要なPの奪取が防止されます。これにより、システム全体のジッターが抑制され、テイルレイテンシの改善に寄与します。<br>リアルタイム性が求められるシステムにおいて、このシステムの揺らぎの低減は、スループット向上に匹敵する価値があると考えられます。</p>
<h2 id="まとめ">まとめ</h2><p>「cgoとは」という観点から実際に享受可能なメリットまで深ぼってみました。</p>
<p>原文では<code>The baseline runtime overhead of cgo calls has been reduced by ~30%.</code>の一行のみがさらっと記載されているのみでしたが、コードの書き換えを一切必要とせずアップグレードするだけで30％のオーバーヘッドを削減でき、素晴らしいアップデートでした。</p>
]]></content>
    <summary type="html">Go1.26でcgoが高速化され、約30%速くなったと公表がありました。これまでなんとなくでcgoを捉えていたので、これを機会にそもそもの仕組みやどのようにオーバーヘッドが短縮されたのか調べてみました。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="C言語" scheme="https://future-architect.github.io/tags/C%E8%A8%80%E8%AA%9E/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.26" scheme="https://future-architect.github.io/tags/Go1-26/"/>
  </entry>
  <entry>
    <title>Go1.26リリース連載：newプリミティブの拡張</title>
    <link href="https://future-architect.github.io/articles/20260202a/"/>
    <id>https://future-architect.github.io/articles/20260202a/</id>
    <published>2026-02-01T15:00:00.000Z</published>
    <updated>2026-02-01T15:00:00.000Z</updated>
    <author><name>辻大志郎</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260202a/top.jpg" alt="" width="400" height="331">

<h2 id="はじめに">はじめに</h2><p>製造エネルギー事業部の辻です。Go1.26ブログ連載 の5本目です。</p>
<p>この記事では、言語仕様のアップデートから <code>new</code> プリミティブの拡張を紹介します。</p>
<h2 id="Go1-25までの-new-の挙動">Go1.25までの <code>new</code> の挙動</h2><p>アップデート内容へ入る前に、Go1.25までの <code>new()</code> の挙動をおさらいしておきます。Go1.25までの <code>new()</code> は、引数に型を指定し、指定された型のゼロ値で初期化し、そのポインタを返す関数でした。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	ptr := <span class="built_in">new</span>(<span class="type">int</span>)</span><br><span class="line">	fmt.Println(*ptr) <span class="comment">// 0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>あくまでゼロ値を作るためのものだったため、特定の値（例えば <code>10</code> や <code>&quot;hoge&quot;</code>）が入ったポインタを作りたい場合は、以下のように2行にわけて書く必要がありました。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line">v := <span class="number">10</span></span><br><span class="line">ptr := &amp;v</span><br></pre></td></tr></table></figure>

<div class="note-container note-info"><span class="note-icon"></span><div>

<p>どうしても1行で書くためのテクニックとしては…</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line">v := &amp;[]<span class="type">int</span>&#123;<span class="number">10</span>&#125;[<span class="number">0</span>]</span><br></pre></td></tr></table></figure>

<p>なども知られているかもしれません。</p>
</div></div>

<p>Go1.18でジェネリクスが導入されてからは以下のようなヘルパー関数を用意することもよくあるプラクティスでした。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ToPtr</span>[<span class="title">T</span> <span class="title">any</span>]<span class="params">(v T)</span></span> *T &#123;</span><br><span class="line">	<span class="keyword">return</span> &amp;v</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>こうしたゼロ値でない値を持つポインタを1行で記述したいニーズは 2014 年頃からありました（#9097 proposal: spec: add &amp;T(v) to allocate variable of type T, set to v, and return address）。</p>
<h2 id="Go1-26の-new-アップデートサマリ">Go1.26の <code>new</code> アップデートサマリ</h2><blockquote>
<p>The built-in new function, which creates a new variable, now allows its operand to be an expression, specifying the initial value of the variable.</p>
</blockquote>
<p>組み込み関数の <code>new()</code> が引数に、型あるいは式を指定でき、変数の初期値を定義できるようになりました。（なお、式にはリテラル（<code>10</code>）、変数（<code>v</code>）、関数呼び出し（<code>f()</code>）、演算（<code>a+b</code>）などが含まれます）</p>
<h3 id="どういう場合に役に立つ？">どういう場合に役に立つ？</h3><p><strong>1.構造体のオプショナル項目の初期化を簡潔に実装できる</strong></p>
<p>構造体のオプショナルなフィールドで、値がない状態を <code>nil</code> で表現することはよくあります。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1s7lsfs-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1s7lsfs-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> Person <span class="keyword">struct</span> &#123;</span><br><span class="line">    Name <span class="type">string</span>   <span class="string">`json:&quot;name&quot;`</span></span><br><span class="line">    Age  *<span class="type">int</span>     <span class="string">`json:&quot;age&quot;`</span> <span class="comment">// 不明な場合は nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>冒頭のようにGo1.25までは関数の戻り値や値リテラルをポインタ型のフィールドに設定するためには一度変数にするなどが必要でした。Go1.26でこのような関数の戻り値のポインタを <code>new()</code> を用いて初期化できるようになります。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-1s7lsfs-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1s7lsfs-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">personJSON</span><span class="params">(name <span class="type">string</span>, born time.Time)</span></span> ([]<span class="type">byte</span>, <span class="type">error</span>) &#123;</span><br><span class="line">	<span class="keyword">return</span> json.Marshal(Person&#123;</span><br><span class="line">		Name: name,</span><br><span class="line">		Age:  <span class="built_in">new</span>(yearsSince(born)), <span class="comment">// 関数を new に渡せる!</span></span><br><span class="line">	&#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">yearsSince</span><span class="params">(t time.Time)</span></span> <span class="type">int</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="type">int</span>(time.Since(t).Hours() / (<span class="number">365.25</span> * <span class="number">24</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p><strong>2.値リテラルからポインタ型へ直接変換できる</strong></p>
<p>1と関連しますが <code>10</code> や <code>&quot;hoge&quot;</code> といったリテラル値のポインタを直接取得できませんでした。構造体の <code>*int</code> や <code>*string</code> 型のフィールドを埋めるために、値からポインタ型に変換するヘルパー関数を用意することがよくありました。AWS SDK for Go v2 などで <code>aws.Int()</code> などを利用していた方も多いのでは、と思います。</p>
<figure class="highlight go"><figcaption><span>to_ptr.go</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Int</span><span class="params">(v <span class="type">int</span>)</span></span> *<span class="type">int</span> &#123;</span><br><span class="line">	<span class="keyword">return</span> ptr.Int(v)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Go 1.26 からは、<code>new()</code> でリテラル値からポインタ型へ直接変換できます。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> User <span class="keyword">struct</span> &#123;</span><br><span class="line">	Name <span class="type">string</span> <span class="string">`json:&quot;name&quot;`</span></span><br><span class="line">	Age  *<span class="type">int</span>   <span class="string">`json:&quot;age&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	u := User&#123;</span><br><span class="line">		Name: <span class="string">&quot;Taro&quot;</span>,</span><br><span class="line">		Age:  <span class="built_in">new</span>(<span class="number">20</span>),</span><br><span class="line">	&#125;</span><br><span class="line">	fmt.Println(u)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>なお、たとえば <code>*int64</code> が欲しい場合は <code>new(int64(20)</code> のように記述できます。<code>new()</code> に任意の型を指定することで、任意の型のポインタを生成できる点もシンプルながら柔軟性がある仕様と言えます。</p>
<h2 id="検討されていたが採用されなかった案たち">検討されていたが採用されなかった案たち</h2><p>#45624 spec: expression to create pointer to simple types のIssueを見ますと、いろいろな案が議論されていました。最終的なアップデートは <code>new()</code> の拡張、という非常にシンプルな形に落ち着いたものの、代替案も興味深かったため、そのいくつかを紹介したいと思います。</p>
<h3 id="new-Type-Value-形式"><code>new(Type, Value)</code> 形式</h3><p><code>new()</code> に型と値を渡してポインタを取得する方法です。Issue #45624 の冒頭で、Rob氏がOption1として挙げており有力候補のようでした。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line">p1 := <span class="built_in">new</span>(<span class="type">int</span>, <span class="number">3</span>)</span><br><span class="line">p2 := <span class="built_in">new</span>(<span class="type">rune</span>, <span class="number">10</span>)</span><br><span class="line">p3 := <span class="built_in">new</span>(Weekday, Tuesday)</span><br><span class="line">p4 := <span class="built_in">new</span>(Name, <span class="string">&quot;unspecified&quot;</span>)</span><br></pre></td></tr></table></figure>

<h4 id="支持されていた点">支持されていた点</h4><ul>
<li>型と初期値が明示されており、何が起きているか読み手にとって曖昧さがない</li>
<li>既存の概念と整合している<ul>
<li><code>make(Type, size)</code> などの既存の組み込み関数との整合が取れている</li>
</ul>
</li>
<li>明確に型推論される<ul>
<li><code>new(3)</code> と <code>new(int64, 3)</code> が区別できる</li>
</ul>
</li>
</ul>
<h4 id="懸念点">懸念点</h4><ul>
<li>冗長である。特にパッケージ型が長い場合に冗長<ul>
<li><code>new(time.Duration, time.Second)</code> など。<code>new(time.Second)</code> としたい、という意見あり</li>
<li><code>3</code> は <code>int</code> であることが自明であるが、<code>new(int, 3)</code> と記述しないといけないのは冗長</li>
</ul>
</li>
</ul>
<p>また、この <code>new(Type, Value)</code> の亜種として <code>new[Type](Value)</code> というジェネリクス風の案もあげられていました。こちらは既存の <code>make()</code> などでは型はジェネリクスで指定しておらず、一貫性がなくなる、という意見がありました。</p>
<h3 id="Type-Value-形式"><code>&amp;Type(Value)</code> 形式</h3><p>Rob氏のOption2として挙げられています。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line">p1 := &amp;<span class="type">int</span>(<span class="number">3</span>)</span><br><span class="line">p2 := &amp;<span class="type">rune</span>(<span class="number">10</span>)</span><br><span class="line">p3 := &amp;Weekday(Tuesday)</span><br><span class="line">p4 := &amp;Name(<span class="string">&quot;unspecified&quot;</span>)</span><br></pre></td></tr></table></figure>

<p><code>&amp;</code> を拡張すると <code>&amp;</code> が式と変数で異なる意味を持ち、良くない、という意見がありました。つまり <code>&amp;</code> は既存の変数のアドレスを取るものだが、拡張した構文では新しいメモリを割り当ててそのアドレスを返すことになり、一貫しないということです。</p>
<h3 id="ref-関数などのヘルパー関数導入"><code>ref()</code> 関数などのヘルパー関数導入</h3><p>値からポインタへ変換する <code>ref()</code> のような関数を、組み込み関数や標準ライブラリとして実装する案です。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line">ref(<span class="number">123</span>)                  <span class="comment">// *int</span></span><br><span class="line">ref(<span class="built_in">make</span>([]<span class="type">string</span>, <span class="number">0</span>, <span class="number">3</span>)) <span class="comment">// *[]string</span></span><br><span class="line">ref(<span class="string">&quot;hello&quot;</span>)              <span class="comment">// *string</span></span><br><span class="line">ref(ref(<span class="type">string</span>))          <span class="comment">// **string</span></span><br><span class="line">ref(os.Stdin.Name())</span><br></pre></td></tr></table></figure>

<p><code>varOf</code> や <code>newOf</code>、<code>ref</code> などいろいろな命名案が議論されていました。<code>ref</code> などは既存のコードでよく使われている命名の可能性があり、衝突の懸念もあったようです。</p>
<p>また、命名に関しては、新しい命名を導入するよりは、既存の <code>new</code> が適当であるとGoのメンバーがコメントされています。</p>
<h2 id="まとめ">まとめ</h2><p>Go1.26 で導入される <code>new</code> の拡張について触れました。既存の <code>new</code> を拡張するというシンプルで美しいアップデート、と感じています。ニーズは多いと思うので多くのGopherにとって嬉しい拡張なのではないでしょうか。</p>
]]></content>
    <summary type="html">言語仕様のアップデートからnewプリミティブの拡張を紹介します。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.26" scheme="https://future-architect.github.io/tags/Go1-26/"/>
  </entry>
  <entry>
    <title>Go 1.26の新GC「Green Tea（緑茶）」解説</title>
    <link href="https://future-architect.github.io/articles/20260130a/"/>
    <id>https://future-architect.github.io/articles/20260130a/</id>
    <published>2026-01-29T15:00:00.000Z</published>
    <updated>2026-01-29T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260130a/Green_Tea_GC開発のタイムライン.png" alt="Green_Tea_GC開発のタイムライン" width="1200" height="563">

<p>Green Tea GCの開発タイムライン（出典: Go公式ブログ）</p>
<h2 id="はじめに">はじめに</h2><p>Go 1.26 リリース連載の 4 本目です。</p>
<p>Go 1.26 がリリースされ、ガベージコレクタ（GC）に大きな変更が加わりました。その名も <strong>Green Tea GC</strong>（緑茶GC）です。</p>
<p>公式ブログによると、この名前は2024年にGoランタイムチームのAustinが日本でカフェ巡りをしながら、大量の抹茶を飲みつつプロトタイプを開発したことに由来しているそうです。</p>
<blockquote>
<p>Green Tea got its name in 2024 when Austin worked out a prototype of an earlier version <strong>while cafe crawling in Japan and drinking LOTS of matcha!</strong> This prototype showed that the core idea of Green Tea was viable. And from there we were off to the races.</p>
</blockquote>
<p>Green Tea GCは Go 1.25 で <code>GOEXPERIMENT=greenteagc</code> として実験的に導入され、Go 1.26 からはデフォルトで有効化されました。本記事では、この新しいGCの仕組みと、実際にどの程度の性能改善が得られるのかを解説します。</p>
<hr>
<h2 id="TL-DR">TL;DR</h2><ul>
<li><strong>10〜40%のGCオーバーヘッド削減</strong>を実現</li>
<li>メモリアクセスの<strong>空間的局所性</strong>を大幅に改善</li>
<li>Intel&#x2F;AMDの最新CPUでは<strong>AVX-512によるベクトル加速</strong>も利用可能</li>
<li>Go 1.26からデフォルト有効（オプトアウト: <code>GOEXPERIMENT=nogreenteagc</code>）</li>
</ul>
<hr>
<h2 id="GoのGCの基本アルゴリズム">GoのGCの基本アルゴリズム</h2><p>GoのGCは <strong>並行マークスイープ（Concurrent Mark-Sweep）</strong> アルゴリズムを採用しています。<br>まず、従来から使われている基本的な仕組みを確認しましょう。</p>
<h3 id="マーキングの仕組み">マーキングの仕組み</h3><p>GoのGCは、プログラムが使用中のオブジェクトを特定するために「マーキング」を行います。</p>
<p>マーキングでは、「ルート」（グローバル変数やスタック上の変数）から参照をたどり、到達できるオブジェクトに「使用中」の印をつけていきます。印がつかなかったオブジェクトは、プログラムから到達不可能なのでGC対象となります。</p>
<p>処理の流れは次のとおりです:</p>
<ol>
<li>ルートから参照されているオブジェクトをワークリストに追加</li>
<li>ワークリストからオブジェクトを取り出し、その中のポインタを調べる（これを「スキャン」と呼ぶ）</li>
<li>見つけたポインタが指すオブジェクトをワークリストに追加</li>
<li>ワークリストが空になるまで繰り返す</li>
</ol>
<div class="code-block"><figure class="highlight txt"><input type="checkbox" id="code-wrap-l9iu3d-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-l9iu3d-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">【マーキング処理の流れ】</span><br><span class="line"></span><br><span class="line">        ルート</span><br><span class="line">          │</span><br><span class="line">          ▼</span><br><span class="line">      ┌───────┐</span><br><span class="line">      │   A   │ ←── スタート</span><br><span class="line">      └───────┘</span><br><span class="line">        │   │</span><br><span class="line">        ▼   ▼</span><br><span class="line">    ┌───────┐ ┌───────┐</span><br><span class="line">    │   B   │ │   C   │</span><br><span class="line">    └───────┘ └───────┘</span><br><span class="line">        │</span><br><span class="line">        ▼</span><br><span class="line">    ┌───────┐</span><br><span class="line">    │   D   │</span><br><span class="line">    └───────┘</span><br><span class="line"></span><br><span class="line">ワークリストの変化:</span><br><span class="line">  Step 1: [A]           ← ルートからAを追加</span><br><span class="line">  Step 2: [B, C]        ← Aをスキャン → B, Cを発見</span><br><span class="line">  Step 3: [C, D]        ← Bをスキャン → Dを発見</span><br><span class="line">  Step 4: [D]           ← Cをスキャン → 新規なし</span><br><span class="line">  Step 5: []            ← Dをスキャン → 完了</span><br></pre></td></tr></table></figure></div>

<h3 id="従来GCの問題点">従来GCの問題点</h3><p>上記のマーキング処理は、公式ブログで「グラフフラッド（graph flood）」と呼ばれています。ポインタをたどってオブジェクトを次々と訪問していく処理です。</p>
<p>しかし、この方法には問題がありました。ワークリストからオブジェクトを取り出してスキャンするたびに、メモリ上のまったく異なる場所にジャンプしてしまうのです。</p>
<div class="code-block"><figure class="highlight txt"><input type="checkbox" id="code-wrap-l9iu3d-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-l9iu3d-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">メモリ空間（ページ単位で区切られている）</span><br><span class="line"></span><br><span class="line">ページ1    ページ2    ページ3    ページ4    ページ5</span><br><span class="line">┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐</span><br><span class="line">│ [A]   │ │   [B] │ │       │ │   [D] │ │ [C]   │</span><br><span class="line">│       │ │       │ │       │ │       │ │       │</span><br><span class="line">└───────┘ └───────┘ └───────┘ └───────┘ └───────┘</span><br><span class="line"></span><br><span class="line">ポインタの参照関係: A → C → B → D</span><br><span class="line">（AがCを参照、CがBを参照、BがDを参照）</span><br><span class="line">                    ↓</span><br><span class="line">GCのアクセス先:  ページ1 → 5 → 2 → 4 とジャンプ</span><br></pre></td></tr></table></figure></div>

<p>ポインタをたどる順序とメモリ上の配置は無関係なため、あちこちに飛び回ることになってしまいます。</p>
<p>CPUには「キャッシュ」という高速な一時メモリがあります。メインメモリ（RAM）へのアクセスは遅いため、よく使うデータをキャッシュに置いて高速化しています。近くのメモリを連続してアクセスすればキャッシュが効きますが、離れた場所をランダムにアクセスすると、毎回メインメモリまで取りに行く必要があります。</p>
<p>公式ブログでは、この状況を「高速道路ではなく市街地を走るようなもの」と表現しています。先が見えず次に何が起こるか予測できないため、CPUは本来の性能を発揮できません。</p>
<blockquote>
<p>Imagine the CPU driving down a road, where that road is your program. The CPU wants to ramp up to a high speed, and to do that it needs to be able to see far ahead of it, and the way needs to be clear. <strong>But the graph flood algorithm is like driving through city streets for the CPU. The CPU can’t see around corners and it can’t predict what’s going to happen next.</strong> To make progress, it constantly has to slow down to make turns, stop at traffic lights, and avoid pedestrians. It hardly matters how fast your engine is because you never get a chance to get going.</p>
</blockquote>
<p>具体的な数字で見ると（公式ブログより）：</p>
<ul>
<li>GC時間の <strong>90%</strong> がマーキングに費やされる</li>
<li>そのマーキング時間の <strong>35%以上</strong> がメモリアクセス待ち（ストール）</li>
<li>メインメモリへのアクセスはキャッシュの <strong>最大100倍遅い</strong></li>
</ul>
<hr>
<h2 id="Green-Teaの仕組み">Green Teaの仕組み</h2><p>Green Tea GCの核心アイデアは非常にシンプルです：</p>
<blockquote>
<p><strong>「オブジェクト単位ではなく、ページ（スパン）単位で作業する」</strong></p>
</blockquote>
<h3 id="ページ蓄積戦略">ページ蓄積戦略</h3><p>従来のGCは、ポインタを見つけるとすぐにそのオブジェクトをスキャンしていました。Green Teaでは異なるアプローチを取ります。</p>
<ol>
<li>ポインタを発見したら、ターゲットオブジェクトが存在する<strong>ページ全体</strong>をワークリストに追加</li>
<li>ページがキューで待機している間に、同じページ内の<strong>他のオブジェクトも蓄積</strong></li>
<li>ページを処理する時に、蓄積されたすべてのオブジェクトを<strong>メモリ順序で一括スキャン</strong></li>
</ol>
<div class="code-block"><figure class="highlight txt"><input type="checkbox" id="code-wrap-l9iu3d-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-l9iu3d-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">【従来のGC】オブジェクト単位でスキャン → ランダムアクセス</span><br><span class="line"></span><br><span class="line">  ページ1      ページ2      ページ3      ページ4</span><br><span class="line">┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐</span><br><span class="line">│ ①       │  │    ③   │  │ ②      │  │      ④ │</span><br><span class="line">└─────────┘  └─────────┘  └─────────┘  └─────────┘</span><br><span class="line">     │            ↑            ↑            ↑</span><br><span class="line">     └────────────┼────────────┘            │</span><br><span class="line">                  └─────────────────────────┘</span><br><span class="line">        アクセス順: ① → ② → ③ → ④（ページ間をジャンプ）</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">【Green Tea GC】ページ単位でスキャン → シーケンシャルアクセス</span><br><span class="line"></span><br><span class="line">  ページ1      ページ2      ページ3      ページ4</span><br><span class="line">┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐</span><br><span class="line">│  ○ ○ ○  │→ │  ○ ○ ○  │→ │  ○ ○ ○  │→ │  ○ ○ ○  │</span><br><span class="line">└─────────┘  └─────────┘  └─────────┘  └─────────┘</span><br><span class="line">        アクセス順: ページ1内を全部 → ページ2内を全部 → ...</span><br></pre></td></tr></table></figure></div>

<p>内部的には、各オブジェクトに「発見済み／スキャン済み」のビットを持たせることで、同じページ内のオブジェクトを効率的に追跡しています。</p>
<h3 id="FIFOキュー">FIFOキュー</h3><p>従来のGCはLIFO（スタック）でワークリストを管理していましたが、Green TeaはFIFO（キュー）を採用しています。</p>
<ul>
<li><strong>LIFO（従来のGC）</strong>: 最後に入れたものを最初に処理 → 蓄積する時間がない</li>
<li><strong>FIFO（Green Tea GC）</strong>: 最初に入れたものを後で処理 → 待機中に同じページの他オブジェクトが蓄積される</li>
</ul>
<h3 id="ベクトル加速">ベクトル加速</h3><p>2020年以降の新しいIntel&#x2F;AMD CPUでは、CPU内蔵の高速演算機能（ベクトル命令）を活用して、追加で約10%の性能改善が得られます。</p>
<p>詳細は公式ドキュメントを参照してください：</p>
<ul>
<li>Go 1.26 Release Notes</li>
<li>The Green Tea Garbage Collector</li>
<li>A Guide to the Go Garbage Collector</li>
</ul>
<p>なお、Green Teaは512バイト以下の小オブジェクトに最適化されており、各プロセッサが独自のスパンキューを持つことで、多コア環境でも効率的にスケールします。</p>
<hr>
<h2 id="実際に動作させて、計測してみる">実際に動作させて、計測してみる</h2><p>実際にGreen Tea GCの効果を確認するため、GC負荷の高いベンチマークプログラムを作成して計測してみました。</p>
<h3 id="ベンチマークコード">ベンチマークコード</h3><div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-l9iu3d-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-l9iu3d-4" title="コードの折り返しを切り替える"></label><figcaption><span>gc_bench.go</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"> <span class="string">&quot;runtime&quot;</span></span><br><span class="line"> <span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> SmallObject <span class="keyword">struct</span> &#123;</span><br><span class="line"> next *SmallObject</span><br><span class="line"> val  [<span class="number">6</span>]<span class="type">int64</span> <span class="comment">// 48 bytes + pointer = ~64 bytes block</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line"> numObjects = <span class="number">10_000_000</span> <span class="comment">// 1000万個のオブジェクト</span></span><br><span class="line"> iterations = <span class="number">100</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"> fmt.Println(<span class="string">&quot;Starting GC Benchmark...&quot;</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> m runtime.MemStats</span><br><span class="line"> runtime.ReadMemStats(&amp;m)</span><br><span class="line"> initialNumGC := m.NumGC</span><br><span class="line"></span><br><span class="line"> start := time.Now()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; iterations; i++ &#123;</span><br><span class="line">  makeGarbage()</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> duration := time.Since(start)</span><br><span class="line"> runtime.ReadMemStats(&amp;m)</span><br><span class="line"></span><br><span class="line"> fmt.Printf(<span class="string">&quot;\n--- Result ---\n&quot;</span>)</span><br><span class="line"> fmt.Printf(<span class="string">&quot;Total Duration: %v\n&quot;</span>, duration)</span><br><span class="line"> fmt.Printf(<span class="string">&quot;Average per iteration: %v\n&quot;</span>, duration/time.Duration(iterations))</span><br><span class="line"> fmt.Printf(<span class="string">&quot;NumGC: %d (this run: %d)\n&quot;</span>, m.NumGC, m.NumGC-initialNumGC)</span><br><span class="line"> fmt.Printf(<span class="string">&quot;TotalPause: %v\n&quot;</span>, time.Duration(m.PauseTotalNs))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 大量の小さなオブジェクトを生成しては破棄する（短寿命オブジェクトの掃き出し負荷テスト）</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">makeGarbage</span><span class="params">()</span></span> &#123;</span><br><span class="line"> <span class="keyword">var</span> head *SmallObject</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; numObjects; i++ &#123;</span><br><span class="line">  <span class="comment">// リンク構造を作ることでスキャナにポインタを追跡させる</span></span><br><span class="line">  head = &amp;SmallObject&#123;next: head, val: [<span class="number">6</span>]<span class="type">int64</span>&#123;<span class="type">int64</span>(i)&#125;&#125;</span><br><span class="line"> &#125;</span><br><span class="line"> _ = head <span class="comment">// keep alive until here</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>このコードのポイント：</p>
<ul>
<li><strong>64バイトの小オブジェクト</strong>: Green Teaの最適化対象となるサイズ</li>
<li><strong>リンクリスト構造</strong>: ポインタ追跡を強制し、GCスキャナに負荷をかける</li>
<li><strong>1000万個 × 100回</strong>: 大量のオブジェクト生成と破棄を繰り返す</li>
</ul>
<h3 id="計測結果">計測結果</h3><p><strong>Go 1.25</strong> と <strong>Go 1.26rc1</strong> でそれぞれ5回ずつ実行し、平均を取りました。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-l9iu3d-5" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-l9iu3d-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="keyword">for</span> i <span class="keyword">in</span> &#123;1..5&#125;; <span class="keyword">do</span> go run gc_bench.go 2&gt;&amp;1 | grep <span class="string">&quot;Total Duration&quot;</span>; <span class="keyword">done</span></span></span><br><span class="line">Total Duration: 42.809022396s</span><br><span class="line">Total Duration: 41.469512574s</span><br><span class="line">Total Duration: 40.128681706s</span><br><span class="line">Total Duration: 41.512944959s</span><br><span class="line">Total Duration: 40.281574518s</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="keyword">for</span> i <span class="keyword">in</span> &#123;1..5&#125;; <span class="keyword">do</span> go1.26rc1 run gc_bench.go 2&gt;&amp;1 | grep <span class="string">&quot;Total Duration&quot;</span>; <span class="keyword">done</span></span></span><br><span class="line">Total Duration: 36.808893283s</span><br><span class="line">Total Duration: 35.833063737s</span><br><span class="line">Total Duration: 36.843265014s</span><br><span class="line">Total Duration: 37.50556513s</span><br><span class="line">Total Duration: 36.551616285s</span><br></pre></td></tr></table></figure></div>

<div class="scroll"><table>
<thead>
<tr>
<th>バージョン</th>
<th>Run 1</th>
<th>Run 2</th>
<th>Run 3</th>
<th>Run 4</th>
<th>Run 5</th>
<th><strong>平均</strong></th>
</tr>
</thead>
<tbody><tr>
<td>Go 1.25</td>
<td>42.81s</td>
<td>41.47s</td>
<td>40.13s</td>
<td>41.51s</td>
<td>40.28s</td>
<td><strong>41.24s</strong></td>
</tr>
<tr>
<td>Go 1.26rc1</td>
<td>36.81s</td>
<td>35.83s</td>
<td>36.84s</td>
<td>37.51s</td>
<td>36.55s</td>
<td><strong>36.71s</strong></td>
</tr>
</tbody></table></div>
<h3 id="結果分析">結果分析</h3><figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">改善: 41.24s → 36.71s</span><br><span class="line">差分: 4.53秒の短縮</span><br><span class="line">改善率: 約11%高速化</span><br></pre></td></tr></table></figure>

<p>また、GC回数の減少も計測できました（ex: 129回 → 109回）。これはGreen TeaのFIFOキュー戦略により、GCがより効率的にメモリを回収できるようになったためだと思われます。</p>
<p>このベンチマークは小オブジェクトのリンクリストという、Green Teaの得意パターンを直撃するワークロードです。実際のアプリケーションでは、ワークロードの特性によって改善率は変動します（公式発表では10〜40%）。</p>
<hr>
<h2 id="まとめ">まとめ</h2><p>Go 1.26のGreen Tea GCについて、変更点を整理します。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>観点</th>
<th>従来のGC</th>
<th>Green Tea GC</th>
</tr>
</thead>
<tbody><tr>
<td>処理単位</td>
<td>オブジェクト</td>
<td>ページ（スパン）</td>
</tr>
<tr>
<td>メモリアクセス</td>
<td>ランダム</td>
<td>シーケンシャル</td>
</tr>
<tr>
<td>キュー戦略</td>
<td>LIFO</td>
<td>FIFO</td>
</tr>
<tr>
<td>キャッシュ効率</td>
<td>—</td>
<td>大幅に改善</td>
</tr>
</tbody></table></div>
<h3 id="導入方法">導入方法</h3><p>Go 1.26では何もしなくてもGreen Tea GCが有効です。もし問題が発生した場合は、以下でオプトアウトできます：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">GOEXPERIMENT=nogreenteagc go build</span><br></pre></td></tr></table></figure>

<h3 id="GCトレースの確認">GCトレースの確認</h3><p>GCの動作を詳しく確認したい場合は：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">GODEBUG=gctrace=1 ./your_program</span><br></pre></td></tr></table></figure>

<h2 id="おわりに">おわりに</h2><p>今回のアイデアの種は、2018年まで遡るようです。公式ブログでは次のように述べられています：</p>
<blockquote>
<p>The seeds of this idea go all the way back to 2018. What’s funny is that everyone on the team thinks someone else thought of this initial idea.</p>
</blockquote>
<p>「チームの全員が、このアイデアは他の誰かが考えたものだと思っている」というのも面白いエピソードですね。</p>
<p>Go 1.26へのアップグレードを検討している方は、ぜひ緑茶GCの効果を体感してみてください。</p>
]]></content>
    <summary type="html">Green Tea GCの開発タイムライン（出典: Go公式ブログ）*Go 1.26 がリリースされ、ガベージコレクタ（GC）に大きな変更が加わりました。その名も Green Tea GC（緑茶GC）です。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="GC" scheme="https://future-architect.github.io/tags/GC/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.26" scheme="https://future-architect.github.io/tags/Go1-26/"/>
  </entry>
  <entry>
    <title>Go 1.26で go fix が面白くなった</title>
    <link href="https://future-architect.github.io/articles/20260129a/"/>
    <id>https://future-architect.github.io/articles/20260129a/</id>
    <published>2026-01-28T15:00:00.000Z</published>
    <updated>2026-01-28T15:00:00.000Z</updated>
    <author><name>真野隼記</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>TIG（Technology Innovation Group）の真野です。</p>
<p>Go 1.26ブログ連載 の3日目は、<code>go fix</code> コマンドのアップデートについて解説します。</p>
<h2 id="go-fix-の出発点">go fix の出発点</h2><p>リリース内容へ入る前に、<code>go fix</code> そのものについて解説します。</p>
<p>まず、Go 1.26で新しく <code>go fix</code> コマンドが追加されたわけではありません。コマンド自体は2011年4月15日に公開されたIntroducing Gofixというブログで紹介され、その翌月リリースのr57 で追加されました。つまり、誕生から15年近く経過する由緒ある（？）ツールとも言えます。</p>
<p>それにも関わらず、 <code>go fix</code> コマンドはあまり有名で無いと思います。なぜでしょうか。</p>
<p>まず、Go言語が1.0に到達したのは、2012年3月28日で、<code>go fix</code> コマンドが紹介されたのはその1年前です。現在と大きく異なり、正式版に向けて週次ベースでリリースしており、破壊的な仕様変更も行われていた時期です。その中には <code>http</code> や <code>os</code> といった利用頻度が高いパッケージのAPI変更も含まれるため、開発者としては追随が大変でした。</p>
<p><code>go fix</code> の出自は、こうしたAPIの破壊的変更にともなる既存コードの書き換えすることです（Goチーム公式からバージョンアップに伴う移行ツールが提供されていたという訳で、ホスピタリティが凄さを感じます）。もちろん、バイナリを書き換えるわけではないので再ビルドが必要ですが、利用者視点では手間という面で、このツールがあるのと無いでは大きな差でしょう。Go開発チームとしても、 <code>go fix</code> という変換ツールがあるこそ、API変更のコストに囚われすぎずより良い言語にすることが集中できた（意訳）という訳で、当時はとても重要な位置づけでした。</p>
<h2 id="バージョン1-0以降の-go-fix">バージョン1.0以降の go fix</h2><p>一方で、Goはバージョンが1.0に到達してからは破壊的な変更が行われなくなりました。</p>
<p>これ自体は良いことですが、<code>go fix</code> の存在感は低下しました。今となっては不要では？みたいな声を聞いたこともあります。実際、1.0以降のリリースノートで <code>go fix</code> の更新は、<code>context</code> の書き換えくらいでした（他にも見落としていたらすいません）。</p>
<ul>
<li>Go 1.8、Go 1.10<ul>
<li><code>golang.org/x/net/context</code> を <code>context</code> に書き換える機能が追加</li>
</ul>
</li>
</ul>
<p>これ以降は更新が無かったため、ほとんどの開発者の意識から外れていたのではないでしょうか。 <code>go fmt</code> や <code>go vet</code> は広く活用されていたのに、不憫な子。</p>
<h2 id="Go-1-26での-go-fix">Go 1.26での go fix</h2><p>さて、Go 1.26です。</p>
<p>リリースノートやIssueであるcmd&#x2F;go: fix: apply fixes from modernizers, inline, and other analyzers #71859 を読むと、以下の点が更新されました。</p>
<ol>
<li><code>go fix</code> の内部実装が <code>golang.org/x/tools/go/analysis</code> という、<code>go vet</code> や <code>gopls</code> が利用しているのと同じフレームワークを使うようになった。型情報や変数のスコープなどを理解して安全な書き換えが可能になるとのこと</li>
<li>従来の機能は廃止された</li>
<li>新しい文法や書き方に自動的に書き換える（モダン化する）ツールになった</li>
</ol>
<p>特に3点目はさらっと書いていますが重要です。標準パッケージのAPIで破壊的変更を修正するツールから、コンパイル上はエラーにならないけど今となっては古く、非推奨になった書き方を変更するツールになりました。先程紹介した、<code>context</code> の書き換えに近いことがメインになると考えると、AIが生成した古いコードを変換したりにも便利そうです。</p>
<h2 id="実際に何を書き換えてくれるのか">実際に何を書き換えてくれるのか</h2><p><code>go fix</code> で追加されるモダン化処理ですが、GoのLanguage Serverである <code>gopls</code> で使われていた実装が利用できるようです。モダン化とインライン化という2大機能があります。</p>
<ol>
<li>モダン化（#75266）<ul>
<li>https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize</li>
<li>Go 1.26時点で24個機能がある</li>
<li>詳細は後述</li>
</ul>
</li>
<li>インライン化（#75267）<ul>
<li>https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inline</li>
<li>単一機能</li>
<li><code>//go:fix inline</code> ディレクティブを関数に付与することで、その関数の利用者のコードを自動的にインライン展開して書き換えることができる</li>
</ul>
</li>
</ol>
<p>ここから先は、実際の動作を見ていきます。</p>
<h2 id="動かしてみる">動かしてみる</h2><p>モダン化からは以下の4つを動かしてみます。</p>
<ul>
<li><strong>rangeint:</strong> <code>for i := 0; i &lt; 10; i++</code> を <code>for i := range 10</code> に</li>
<li><strong>minmax:</strong> <code>if a &gt; b &#123; m = a &#125;</code> を <code>m = max(a, b)</code> に</li>
<li><strong>any:</strong> <code>interface&#123;&#125;</code> を <code>any</code> に</li>
<li><strong>slicessort:</strong> <code>sort.Slice</code> を Go 1.21で追加された <code>slices.Sort</code> に</li>
</ul>
<p>さらにインライン化を試すという流れを考えています。</p>
<h3 id="環境情報">環境情報</h3><p><code>1.26rc2</code> で動かします。RCバージョンの場合、<code>go.mod</code> でGoバージョンを1.26にしておく必要があります。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go version</span></span><br><span class="line">go version go1.26rc2 linux/amd64</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go mod edit -go=1.26</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cat</span> go.mod</span></span><br><span class="line">module blogsample</span><br><span class="line"></span><br><span class="line">go 1.26</span><br></pre></td></tr></table></figure>

<h3 id="1-モダン化">1. モダン化</h3><p>ちょっと古くさいファイルを用意します。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-e1vliq-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-e1vliq-1" title="コードの折り返しを切り替える"></label><figcaption><span>main.go</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;sort&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	<span class="comment">// 1. 古いループ（range over int にできるはず）</span></span><br><span class="line">	<span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">5</span>; i++ &#123;</span><br><span class="line">		fmt.Println(i)</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 2. max(a, b) になるはず</span></span><br><span class="line">	<span class="keyword">var</span> m, a, b = <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span></span><br><span class="line">	<span class="keyword">if</span> a &gt; b &#123;</span><br><span class="line">		m = a</span><br><span class="line">	&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">		m = b</span><br><span class="line">	&#125;</span><br><span class="line">	fmt.Println(m)</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 3. interface&#123;&#125; (any にできるはず)</span></span><br><span class="line">	<span class="keyword">var</span> x <span class="keyword">interface</span>&#123;&#125; = <span class="string">&quot;hello&quot;</span></span><br><span class="line">	fmt.Println(x)</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 4. 古いソート (slices.Sort にできるはず)</span></span><br><span class="line">	nums := []<span class="type">int</span>&#123;<span class="number">3</span>, <span class="number">1</span>, <span class="number">2</span>&#125;</span><br><span class="line">	sort.Ints(nums)</span><br><span class="line">	fmt.Println(nums)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p><code>go fix</code> コマンドを実行します。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">go fixの実行</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go fix ./...</span></span><br></pre></td></tr></table></figure>

<p>差分です。おー、これがモダン化..!!</p>
<div class="code-block"><figure class="highlight diff"><input type="checkbox" id="code-wrap-e1vliq-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-e1vliq-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">$ git diff</span><br><span class="line"><span class="comment">diff --git a/modern/main.go b/modern/main.go</span></span><br><span class="line"><span class="comment">index 4217b13..147e77f 100644</span></span><br><span class="line"><span class="comment">--- a/modern/main.go</span></span><br><span class="line"><span class="comment">+++ b/modern/main.go</span></span><br><span class="line"><span class="meta">@@ -2,30 +2,26 @@</span> package main</span><br><span class="line"></span><br><span class="line"> import (</span><br><span class="line">        &quot;fmt&quot;</span><br><span class="line"><span class="deletion">-       &quot;sort&quot;</span></span><br><span class="line"><span class="addition">+       &quot;slices&quot;</span></span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> func main() &#123;</span><br><span class="line">        // 1. 古いループ（range over int にできるはず）</span><br><span class="line"><span class="deletion">-       for i := 0; i &lt; 5; i++ &#123;</span></span><br><span class="line"><span class="addition">+       for i := range 5 &#123;</span></span><br><span class="line">                fmt.Println(i)</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        // 2. max(a, b) になるはず</span><br><span class="line">        var m, a, b = 1, 2, 3</span><br><span class="line"><span class="deletion">-       if a &gt; b &#123;</span></span><br><span class="line"><span class="deletion">-               m = a</span></span><br><span class="line"><span class="deletion">-       &#125; else &#123;</span></span><br><span class="line"><span class="deletion">-               m = b</span></span><br><span class="line"><span class="deletion">-       &#125;</span></span><br><span class="line"><span class="addition">+       m = max(a, b)</span></span><br><span class="line">        fmt.Println(m)</span><br><span class="line"></span><br><span class="line">        // 3. interface&#123;&#125; (any にできるはず)</span><br><span class="line"><span class="deletion">-       var x interface&#123;&#125; = &quot;hello&quot;</span></span><br><span class="line"><span class="addition">+       var x any = &quot;hello&quot;</span></span><br><span class="line">        fmt.Println(x)</span><br><span class="line"></span><br><span class="line">        // 4. 古いソート (slices.Sort にできるはず)</span><br><span class="line">        nums := []int&#123;3, 1, 2&#125;</span><br><span class="line"><span class="deletion">-       sort.Ints(nums)</span></span><br><span class="line"><span class="addition">+       slices.Sort(nums)</span></span><br><span class="line">        fmt.Println(nums)</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure></div>

<p><code>go vet</code> と似ている使い勝手で、最新のGoの流儀に合わせてくれるのは嬉しい感じがします。 <code>-diff</code> コマンドで差分を出すこともできるので、CIでの使い勝手も良いと思います。</p>
<h3 id="2-インライン化">2. インライン化</h3><p>従来、ライブラリ提供側の視点で、関数の非推奨化はできましたが、あくまで非推奨と伝えるだけで一括で変換などは行えませんでした。インライン化はそれを支援する方法です。</p>
<figure class="highlight go"><figcaption><span>lib/lib.go</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> lib</span><br><span class="line"></span><br><span class="line"><span class="comment">//go:fix inline</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">OldFunc</span><span class="params">(s <span class="type">string</span>)</span></span> <span class="type">string</span> &#123;</span><br><span class="line">	<span class="keyword">return</span> NewFunc(s, <span class="literal">true</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewFunc</span><span class="params">(s <span class="type">string</span>, flag <span class="type">bool</span>)</span></span> <span class="type">string</span> &#123;</span><br><span class="line">	<span class="keyword">return</span> fmt.Sprint(s, flag)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight go"><figcaption><span>main.go</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;your-module/library&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    lib.OldFunc(<span class="string">&quot;hello&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>go fix ./...</code> を実行すると、<code>main.go</code> が以下のように書き換えられます。</p>
<figure class="highlight diff"><table><tr><td class="code"><pre><span class="line">$ git diff</span><br><span class="line"><span class="comment">diff --git a/inline/main.go b/inline/main.go</span></span><br><span class="line"><span class="comment">index ead5e9d..3448403 100644</span></span><br><span class="line"><span class="comment">--- a/inline/main.go</span></span><br><span class="line"><span class="comment">+++ b/inline/main.go</span></span><br><span class="line"><span class="meta">@@ -5,5 +5,5 @@</span> import (</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> func main() &#123;</span><br><span class="line"><span class="deletion">-       lib.OldFunc(&quot;hello&quot;)</span></span><br><span class="line"><span class="addition">+       lib.NewFunc(&quot;hello&quot;, true)</span></span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p>ライブラリ提供者側の視点としては、公開した関数を変更する場合に機械的にマイグレーションする手段ができたということで、心理的に余裕が生まれるのではないでしょうか。</p>
<p>ちなみにですが、従来の <code>// Deprecated</code> のコメントとの併用も可能です。</p>
<figure class="highlight diff"><figcaption><span>lib/lib.go</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="addition">+// Deprecated: Use NewFunc instead.</span></span><br><span class="line">//go:fix inline</span><br><span class="line">func OldFunc(s string) string &#123;</span><br><span class="line">	return NewFunc(s, true)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>呼び出し側は次のように取り消し線などで、非推奨であることがフィードバックされます。</p>
<img fetchpriority="high" src="/images/2026/20260129a/image.png" alt="image.png" width="863" height="568">

<p>基本的には、 <code>//go:fix inline</code> を追加するときは、 <code>// Deprecated</code> もセットで運用することになるのかなと予測します。</p>
<h2 id="golangci-lintでも-–fix-オプションがあるけど使い分けは？">golangci-lintでも –fix オプションがあるけど使い分けは？</h2><p><code>golangci-lint run --fix</code> などとすれば、Golangci-lintもコードの置換も行ってくれます。</p>
<p>リンター一覧で <code>Autofix</code> タグがついているものがその対象です。</p>
<p>モダン化はいくつか重複している機能もありそうですが、詳しく見ていません。おそらく、衝突するような機能はgolangci-lint 側で無効化&#x2F;修正されると思いますので（根拠はなく予想です）、まずは <code>go fix</code> と <code>golangci-lint run --fix</code> の両方を実行してみて試すのが良いのではないかと思いました。</p>
<p>インライン化は該当の機能はないのでこちらについては <code>go fix</code> の利用が必須になるかと思いした。</p>
<h2 id="さいごに">さいごに</h2><p>コードレビューなどでより新しい書き方をsuggestするというのは、あまり創造的では無いと思っていました。これが <code>go fix</code> でかなり省略されるということで、AIが古いコードを出してきても矯正できる点は良いと思います（<code>go fix</code> で直してまた古いコードに書き換えられたりはあるかもですが）。</p>
<p>とりあえず、 <code>go fmt</code>、<code>go vet</code>、<code>go fix</code> の3点セットで使っていこうと思いました。</p>
]]></content>
    <summary type="html">go fixコマンドのアップデートについて解説します。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.26" scheme="https://future-architect.github.io/tags/Go1-26/"/>
  </entry>
  <entry>
    <title>Go 1.26 リリース連載 Goroutine Leak Profiles</title>
    <link href="https://future-architect.github.io/articles/20260128a/"/>
    <id>https://future-architect.github.io/articles/20260128a/</id>
    <published>2026-01-27T15:00:00.000Z</published>
    <updated>2026-01-27T15:00:00.000Z</updated>
    <author><name>武田大輝</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260128a/top.jpg" alt="" height="1024" width="1024">

<h2 id="はじめに">はじめに</h2><p>Go 1.26 リリース連載の 2 本目です。</p>
<p>本記事では Go 1.26 で追加された Goroutine Leak Profiles について紹介します。</p>
<h2 id="アップデートの概要">アップデートの概要</h2><p>goroutine のリークを検知するためのプロファイルが <code>runtime/pprof</code> に追加されました。<br>これまでは Uber 社が公開している goleak などを用いてリークを検知するのが一般的なプラクティスでしたが、Go 標準のプロファイル機能を利用して検知ができるようになりました。</p>
<p>リリースノート を参照し、ポイントだけ先にまとめると次の通りです。</p>
<ul>
<li><code>runtime/pprof</code> に新しいプロファイル <code>goroutineleak</code> が追加された</li>
<li><code>net/http/pprof</code> のエンドポイントとして <code>/debug/pprof/goroutineleak</code> も追加された</li>
<li>Go 1.26 では <code>GOEXPERIMENT=goroutineleakprofile</code> を付けてビルドした場合のみ利用可能となる（実験的機能）</li>
<li>Go 1.27 でデフォルト有効化が検討されている</li>
</ul>
<p>関連する主要な Proposal や PR は次の通りです。</p>
<ul>
<li>Proposal<br>https://github.com/golang/go/issues/74609<br>https://github.com/golang/go/issues/75280</li>
<li>Design Document<br>https://go.googlesource.com/proposal/+/master/design/74609-goroutine-leak-detection-gc.md</li>
<li>PR<br>https://github.com/golang/go/pull/74622</li>
<li>Change List<br>https://go-review.googlesource.com/c/go/+/688335</li>
</ul>
<h2 id="そもそもの話">そもそもの話</h2><p>本題へ入る前に、初心者向けに goroutine リークと Go のプロファイリング（<code>runtime/pprof</code>）について、軽くおさらいします。</p>
<h3 id="goroutine-リークとは">goroutine リークとは</h3><p>goroutine リーク（goroutine leak）は、ざっくり言うと「本来は不要になった goroutine が、適切に終了されず放置されたまま（実質的に）生き続ける」状態です。</p>
<p>リークした goroutine はスタックや参照しているヒープオブジェクトを保持し続け、長期的にはメモリ使用量や GC 負荷に影響します。通常 goroutine リークは、プログラムがすぐに停止したりエラーになったりしないため、問題に気付きにくいという点でやっかいです。</p>
<p>典型例は次のような3ケースです。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-ykd2se-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ykd2se-1" title="コードの折り返しを切り替える"></label><figcaption><span>① 受信側がいないのに送信する</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">leak</span><span class="params">()</span></span> &#123;</span><br><span class="line">	ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line">	<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">		<span class="comment">// 送信を待つ</span></span><br><span class="line">		ch &lt;- <span class="number">1</span></span><br><span class="line">	&#125;()</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 何かしらの処理でエラーが発生した場合、受信が行われず goroutine がリークする</span></span><br><span class="line">	<span class="keyword">if</span> err := something(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span></span><br><span class="line">	&#125;</span><br><span class="line">	fmt.Println(&lt;-ch)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-ykd2se-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ykd2se-2" title="コードの折り返しを切り替える"></label><figcaption><span>② 送信側がいないのに受信する</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">leak</span><span class="params">()</span></span> &#123;</span><br><span class="line">	ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line"></span><br><span class="line">	<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">		<span class="comment">// 受信を待つ</span></span><br><span class="line">		fmt.Println(&lt;-ch)</span><br><span class="line">	&#125;()</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 何かしらの処理でエラーが発生した場合、送信が行われず goroutine がリークする</span></span><br><span class="line">	<span class="keyword">if</span> err := something(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span></span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	ch &lt;- <span class="number">1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<figure class="highlight go"><figcaption><span>③ close されない channel を使う</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">leak</span><span class="params">()</span></span> &#123;</span><br><span class="line">	ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line"></span><br><span class="line">	<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">		<span class="comment">// channel が close されるまで受信し続ける</span></span><br><span class="line">		<span class="keyword">for</span> x := <span class="keyword">range</span> ch &#123;</span><br><span class="line">			fmt.Println(x)</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;()</span><br><span class="line"></span><br><span class="line">	ch &lt;- <span class="number">1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="プロファイリング（runtime-pprof）とは">プロファイリング（runtime&#x2F;pprof）とは</h3><p><code>runtime/pprof</code> は、Go ランタイムが持つ各種プロファイルを取得するための標準ライブラリです。<br>具体的には次のようなプロファイルが用意されています。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>名前</th>
<th>種別</th>
<th>説明</th>
</tr>
</thead>
<tbody><tr>
<td><code>goroutine</code></td>
<td>スナップショット型</td>
<td>現在存在するすべての goroutine のスタックトレース</td>
</tr>
<tr>
<td><code>heap</code></td>
<td>スナップショット型</td>
<td>生存オブジェクトに対するメモリ割り当てのサンプリング</td>
</tr>
<tr>
<td><code>allocs</code></td>
<td>スナップショット型</td>
<td>過去に行われたすべてのメモリ割り当てのサンプリング</td>
</tr>
<tr>
<td><code>block</code></td>
<td>スナップショット型</td>
<td>channel やロックなどの同期処理で待ち状態になったスタックトレース</td>
</tr>
<tr>
<td><code>mutex</code></td>
<td>スナップショット型</td>
<td>競合しているミューテックスを保持している側のスタックトレース</td>
</tr>
<tr>
<td><code>threadcreate</code></td>
<td>スナップショット型</td>
<td>OS スレッド生成時のスタックトレース</td>
</tr>
<tr>
<td><code>profile</code></td>
<td>期間収集型</td>
<td>一定期間にわたって収集された CPU プロファイル</td>
</tr>
<tr>
<td><code>trace</code></td>
<td>期間収集型</td>
<td>一定期間の実行トレース（スケジューラ／ネットワーク等）</td>
</tr>
</tbody></table></div>
<p>スナップショット型のプロファイルは取得時点におけるプログラムの状態を切り取って記録するのに対し、期間収集型のプロファイルは一定期間にわたる実行状況を収集・記録します。</p>
<p>各プロファイルを取得する方法はいくつかありますが、<code>net/http/pprof</code> を使うと、これらを HTTP のエンドポイント経由で公開・取得できます。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;net/http&quot;</span></span><br><span class="line">	_ <span class="string">&quot;net/http/pprof&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	http.ListenAndServe(<span class="string">&quot;127.0.0.1:6060&quot;</span>, <span class="literal">nil</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>各種プロファイルは <code>go tool pprof</code> コマンドで HTTP エンドポイント（<code>/debug/pprof/$&#123;プロファイル名&#125;</code>）を指定することで確認できます。<br>たとえば heap プロファイルを確認するコマンドは次の通りです。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-ykd2se-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ykd2se-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go tool pprof http://localhost:6060/debug/pprof/heap</span></span><br><span class="line">Fetching profile over HTTP from http://localhost:6060/debug/pprof/heap</span><br><span class="line">...</span><br><span class="line">File: main</span><br><span class="line">Build ID: 0692a5c786cbac9e9d379f2de2112b6066d8673c</span><br><span class="line">Type: inuse_space</span><br><span class="line">Time: 2026-01-19 15:15:56 UTC</span><br><span class="line">Entering interactive mode (type &quot;help&quot; for commands, &quot;o&quot; for options)</span><br></pre></td></tr></table></figure></div>

<p>このコマンドを実行すると、指定した HTTP エンドポイントからプロファイルを取得し、解析用の対話モード（pprof シェル）が起動します。</p>
<p>対話モードでは、<code>top</code> や <code>list</code> などのコマンドを使ってプロファイルの内容を確認できます。</p>
<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-ykd2se-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ykd2se-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">(pprof) top</span><br><span class="line">Showing nodes accounting <span class="keyword">for</span> 3636.87kB, 100% of 3636.87kB total</span><br><span class="line">Showing top 10 nodes out of 38</span><br><span class="line">flat flat% <span class="built_in">sum</span>% cum cum%</span><br><span class="line">902.59kB 24.82% 24.82% 2097.87kB 57.68% compress/flate.NewWriter</span><br><span class="line">650.62kB 17.89% 42.71% 1195.29kB 32.87% compress/flate.(*compressor).init</span><br><span class="line">544.67kB 14.98% 57.68% 544.67kB 14.98% compress/flate.newDeflateFast (inline)</span><br><span class="line">513.12kB 14.11% 71.79% 513.12kB 14.11% compress/flate.(*huffmanEncoder).generate</span><br><span class="line">513kB 14.11% 85.90% 513kB 14.11% runtime.mallocgc</span><br><span class="line">512.88kB 14.10% 100% 512.88kB 14.10% <span class="built_in">sync</span>.(*Pool).pinSlow</span><br><span class="line">0 0% 100% 513.12kB 14.11% compress/flate.(*Writer).Close</span><br><span class="line">0 0% 100% 513.12kB 14.11% compress/flate.(*compressor).close</span><br><span class="line">0 0% 100% 513.12kB 14.11% compress/flate.(*compressor).encSpeed</span><br><span class="line">0 0% 100% 513.12kB 14.11% compress/flate.(*huffmanBitWriter).indexTokens</span><br></pre></td></tr></table></figure></div>

<p>なお <code>go tool pprof</code> を利用せずとも、<code>debug=1</code> や <code>debug=2</code> を指定すれば、ブラウザや curl からプロファイルをテキスト形式で確認できます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-ykd2se-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ykd2se-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl http://localhost:6060/debug/pprof/heap?debug=1</span></span><br><span class="line">heap profile: 2: 2064 [14: 3192928] @ heap/1048576</span><br><span class="line">0: 0 [1: 48] @ 0x200a9c 0x200ac1 0x213a50 0x206bcc 0x20ad60 0xa9654</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">      0x200a9b        net/textproto.NewReader+0x8b            /usr/local/go/src/net/textproto/reader.go:38</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">      0x200ac0        net/http.newTextprotoReader+0xb0        /usr/local/go/src/net/http/request.go:1044</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">      0x213a4f        net/http.readRequest+0x2f               /usr/local/go/src/net/http/request.go:1080</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">      0x206bcb        net/http.(*conn).readRequest+0x1db      /usr/local/go/src/net/http/server.go:1005</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">      0x20ad5f        net/http.(*conn).serve+0x31f            /usr/local/go/src/net/http/server.go:1995</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure></div>

<h2 id="実際に試してみる">実際に試してみる</h2><p>前提となる goroutine リークやプロファイルが理解できたところで、実際に <code>goroutineleak</code> プロファイルを試してみましょう。<br>先ほどリークの例として挙げた「close されない channel を使う」コードを利用します。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-ykd2se-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ykd2se-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;net/http&quot;</span></span><br><span class="line">	_ <span class="string">&quot;net/http/pprof&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">leak</span><span class="params">()</span></span> &#123;</span><br><span class="line">	ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line">	<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">		<span class="comment">// channel が close されるまで受信し続ける</span></span><br><span class="line">		<span class="keyword">for</span> x := <span class="keyword">range</span> ch &#123;</span><br><span class="line">			fmt.Println(x)</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;()</span><br><span class="line">	ch &lt;- <span class="number">1</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	leak()</span><br><span class="line">	<span class="keyword">if</span> err := http.ListenAndServe(<span class="string">&quot;127.0.0.1:6060&quot;</span>, <span class="literal">nil</span>); err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="built_in">panic</span>(err)</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>Go 1.26 では実験的な機能（Experimental）となるため、まず <code>GOEXPERIMENT=goroutineleakprofile</code> を付けてビルド後、起動します。<br>まずは <code>debug=1</code> を付与して簡易的なテキスト形式で確認してみましょう。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-ykd2se-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ykd2se-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl http://localhost:6060/debug/pprof/goroutineleak?debug=1</span></span><br><span class="line">goroutineleak profile: total 1</span><br><span class="line">1 @ 0xa261c 0x26e20 0x26994 0x244f6c 0xa9ab4</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">      0x244f6b        main.leak.func1+0x6b    /workspaces/tech-blog-go-1.26-feature/profile/main.go:13</span></span><br></pre></td></tr></table></figure></div>

<p>1つの goroutine がリークしていることがわかります。</p>
<p><code>debug=2</code> を付与して確認すると、現在存在する goroutine の完全なスタックトレースの一覧を確認できます。<br>リークを確認するには <code>(leaked)</code> が付いている goroutine を見つけることが重要です。<br><code>goroutine 21 [chan receive (leaked)]:</code> という出力のとおり、channel の受信でブロックが発生し、リークしていることがわかります。</p>
<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-ykd2se-8" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ykd2se-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">curl http://localhost:6060/debug/pprof/goroutineleak?debug=2</span><br><span class="line">...</span><br><span class="line">goroutine 21 [chan receive (leaked)]:</span><br><span class="line">main.leak.func1()</span><br><span class="line">        /workspaces/tech-blog-go-1.26-feature/profile/main.go:13 +0x6c</span><br><span class="line">created by main.leak <span class="keyword">in</span> goroutine 1</span><br><span class="line">        /workspaces/tech-blog-go-1.26-feature/profile/main.go:12 +0x74</span><br><span class="line">...</span><br></pre></td></tr></table></figure></div>

<p>それでは次に、先ほどのコードにクローズ処理を入れることでリークを解消して試してみます。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-ykd2se-9" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ykd2se-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;net/http&quot;</span></span><br><span class="line">	_ <span class="string">&quot;net/http/pprof&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">leak</span><span class="params">()</span></span> &#123;</span><br><span class="line">	ch := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line">	<span class="comment">// クローズ処理を追加</span></span><br><span class="line">	<span class="keyword">defer</span> <span class="built_in">close</span>(ch)</span><br><span class="line">	<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">		<span class="keyword">for</span> x := <span class="keyword">range</span> ch &#123;</span><br><span class="line">			fmt.Println(x)</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;()</span><br><span class="line">	ch &lt;- <span class="number">1</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	leak()</span><br><span class="line">	<span class="keyword">if</span> err := http.ListenAndServe(<span class="string">&quot;127.0.0.1:6060&quot;</span>, <span class="literal">nil</span>); err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="built_in">panic</span>(err)</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>プロファイルからも goroutine リークが発生していないことが確認できます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-ykd2se-10" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ykd2se-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl http://localhost:6060/debug/pprof/goroutineleak?debug=1</span></span><br><span class="line">goroutineleak profile: total 0</span><br></pre></td></tr></table></figure></div>

<h2 id="リーク検知の背後にある思想やしくみ">リーク検知の背後にある思想やしくみ</h2><p>リーク検知の設計思想などをつかむために Proposal（#74609）や Design Doc を見てみます。</p>
<p>リークを検知するためのアプローチのざっくりとした理解としては、通常の GC とは異なるリーク検知用の GC サイクルを実行し、到達可能な（実行される可能性のある）goroutine をマークすることで、どこからも到達できない goroutine を検出するようなイメージになります。</p>
<p>リーク判定のために GC サイクルを走らせるため、本番環境における利用は原則さけ、ローカルや本番に近しい環境での調査に使うのがよさそうです。</p>
<p>なお、このアプローチは「検知されたものは確実にリークである（偽陽性が出ない）」ことを重視しており、逆に「リークしているけど検知できない（偽陰性）」ケースはあり得るとされています。</p>
<blockquote>
<p>The advantage of this approach over other goroutine leak detection techniques is that it can be leveraged, with a minimal performance cost, in regular Go systems, e.g., production services. It is also theoretically sound, i.e., there are no false positives. Its primary limitation is that its effectiveness is reduced the more heap resources are over-exposed in memory, i.e., pair-wise reachable.</p>
</blockquote>
<p>ここまで見るとテストの実行前後で goroutine スタックを比較することでリークを検知する <code>goleak</code> のアプローチとは根本的に異なることがわかります。</p>
<p>その意味ではどちらかがどちらかに取って代わるものではなく、もしかしたら相互補完的な関係性にあると言えるかもしれません（例. ユニットテストや CI では <code>goleak</code> を利用し、より本番に近い環境での診断には <code>goroutineleak</code> プロファイルを使用するなど）。</p>
<h2 id="リーク検知されないケース">リーク検知されないケース</h2><p>先ほど説明した通り <code>goroutineleak</code> プロファイルによる検知は「偽陽性を出さない」方向に設計されており、リークしているけど検知できないケースがあります。<br>たとえばグローバル変数として channel を保持する場合は、到達可能（実行される可能性がある）と判断されます。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-ykd2se-11" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ykd2se-11" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;net/http&quot;</span></span><br><span class="line">	_ <span class="string">&quot;net/http/pprof&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// グローバルに保持される channel</span></span><br><span class="line"><span class="keyword">var</span> gch = <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">leak</span><span class="params">()</span></span> &#123;</span><br><span class="line">	<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">		<span class="keyword">for</span> x := <span class="keyword">range</span> gch &#123;</span><br><span class="line">			fmt.Println(x)</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;()</span><br><span class="line">	gch &lt;- <span class="number">1</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	leak()</span><br><span class="line">	<span class="keyword">if</span> err := http.ListenAndServe(<span class="string">&quot;127.0.0.1:6060&quot;</span>, <span class="literal">nil</span>); err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="built_in">panic</span>(err)</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>次のとおりこのコードはリークとして検知されません。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-ykd2se-12" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ykd2se-12" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl http://localhost:6060/debug/pprof/goroutineleak?debug=1</span></span><br><span class="line">goroutineleak profile: total 0</span><br></pre></td></tr></table></figure></div>

<h2 id="おわりに">おわりに</h2><p>ここまで見てきたとおり、Go 1.26 で追加された <code>goroutineleak</code> プロファイルは、なかなか追いづらかった goroutine リークを検知できる標準機能です。</p>
<p>Go 1.26 時点では実験的機能という位置付けですが、Go 1.27 でデフォルトで有効になるのが待ち遠しいですね。</p>
<h2 id="参考">参考</h2><ul>
<li>Dynamic Partial Deadlock Detection and Recovery via Garbage Collection</li>
<li>Detecting Goroutine Leaks via the Go Garbage Collector</li>
</ul>
]]></content>
    <summary type="html">Go 1.26 で追加された Goroutine Leak Profiles について紹介します。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.26" scheme="https://future-architect.github.io/tags/Go1-26/"/>
  </entry>
  <entry>
    <title>Go 1.26連載：インデックス＋SIMD</title>
    <link href="https://future-architect.github.io/articles/20260127a/"/>
    <id>https://future-architect.github.io/articles/20260127a/</id>
    <published>2026-01-26T15:00:00.000Z</published>
    <updated>2026-01-26T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260127a/top.jpg" alt="" width="615" height="615">

<p>Go 1.26のリリースの足音が聞こえてきたので1.26の新機能のうち、気になる機能を有志で紹介していく連載です。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="center">Date</th>
<th align="left">Title</th>
<th align="left">Author</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1&#x2F;27</td>
<td align="left">インデックス+SIMDこの記事</td>
<td align="left">渋川</td>
</tr>
<tr>
<td align="center">1&#x2F;28</td>
<td align="left">goroutine leak profiler</td>
<td align="left">武田さん</td>
</tr>
<tr>
<td align="center">1&#x2F;29</td>
<td align="left">go fix</td>
<td align="left">真野さん</td>
</tr>
<tr>
<td align="center">1&#x2F;30</td>
<td align="left">GCの改善</td>
<td align="left">棚井さん</td>
</tr>
<tr>
<td align="center">2&#x2F;2</td>
<td align="left">new(プリミティブ)</td>
<td align="left">辻さん</td>
</tr>
<tr>
<td align="center">2&#x2F;3</td>
<td align="left">runtime&#x2F;secret</td>
<td align="left">島ノ江さん</td>
</tr>
<tr>
<td align="center">2&#x2F;5</td>
<td align="left">CGo呼び出し高速化</td>
<td align="left">宮崎さん</td>
</tr>
</tbody></table></div>
<h2 id="1-26の新機能のダイジェスト">1.26の新機能のダイジェスト</h2><p>久々に結構大きめな変更が多くなっていますね。</p>
<ul>
<li>new(プリミティブ)</li>
<li>1.25で導入されたGCが有効化</li>
<li>cgo, memory allocate高速化</li>
<li>goroutine leak profiler</li>
<li>compilerがスライスをよりスタックに割り当てられるように</li>
<li>SIMDを扱う実験的パッケージ（AMD64のみサポート）</li>
<li>runtime&#x2F;secretという機密情報を扱う実験的パッケージ</li>
<li>image&#x2F;jpeg が高速に</li>
<li>io.ReadAllの消費メモリが少なく</li>
<li>log&#x2F;slogで複数の出力先に一気に出力できるMultiHandlerが追加</li>
<li>testing.ArtifactDir</li>
<li>暗号アルゴリズムの更新たくさん</li>
</ul>
<p>全体的に高速化が行われています。なのでアップデートするだけで恩恵がいろいろあります。</p>
<p>テストのArtifactDir()は<code>-artifacts</code>フラグを付けてテストを実行すると、<code>T.ArtifactDir()</code>メソッドが中間生成物の置き場を用意してくれて、そこに格納することであとから検死できるようになるというものです。以前、TempDir()が使いにくいというのを技術ブログで書きましたが、それのソリューションが提供された感じですね。</p>
<h2 id="SIMD">SIMD</h2><p>それでは、今回追加された<code>simd/archsimd</code>パッケージについて紹介します。</p>
<p><strong>1&#x2F;29追記</strong>: まだ実験的機能で、コンパイル時に<code>GOEXPERIMENT=simd</code>という環境変数の設定が必要です。</p>
<p>SIMDというのは1つの命令で複数のデータをまとめて処理する命令セットです。4つ、8つ・・・のデータをまとめて足し算したり、掛け算したり。行列計算などを高速に行うのに使います。</p>
<p>インテル系では主に以下のようにSIMDの命令セットの世代があります。それぞれとレジスタサイズの対応は以下の通りです。</p>
<ul>
<li>MMX: 64ビット</li>
<li>SSE: 128ビット</li>
<li>AVX: 256ビット</li>
<li>AVX512: 512ビット</li>
</ul>
<p>なお、AVX512はそれほど大量のデータを処理する必要とするアプリケーションが一部に限られるせいか、10th&#x2F;11th Gen Core CPUでは対応していましたが、その後のチップからは取り除かれてしまいました。それだけ半導体を使うのに使用されないのであればその分通常のコア数を増やそうという感じですね。Eコア・Pコアに分かれた時代からは使えなくなりました。あとはCPUだけが早くてもメモリ転送がボトルネックになりそうですしね。</p>
<p>など、上記の命令セットの名前はインテルですが、AMD、ARM(NEON)なども備えています。</p>
<p>例えば、AVXは256ビットですが、これを8ビットx32本と使ったり、16ビットx16本として使ったり色々できます。simd&#x2F;archsimdパッケージを見ると、いろんな型がありますが、よく見ると、Float32x8といった型になっております。たくさんありますが、データ型と要素数の組み合わせになっております。合計のビット数は128ビット、256ビット、512ビットであることがわかります。だいたい、和だったり、どのような演算をするかはメソッドで提供されていますが、int8&#x2F;uint8などは和はあっても積などはないなど、型によって使えるメソッドに違いがあります。</p>
<p>なお、Goがバイナリを最後に生成するのに使うアセンブラは、アーキテクチャ限定でSIMD命令にはいくつか対応していたので、コードを <code>.go</code> ではなく、 <code>.s</code> で作成すればSIMDは今までも活用できました。Go 1.24で導入されたmapの新アルゴリズムのSwiss Tableは、SIMDを活用して高速検索するというものでした。サードパーティのライブラリでSIMDを使った行列演算ライブラリなどもいくつかありましたが、それが今回公式でできるようになりました。</p>
<h2 id="ベクトルの内積計算">ベクトルの内積計算</h2><p>データ作成は後で説明するのですが、embedding化で作ったドキュメントのベクトルデータと、検索用語のベクトルデータの内積を取ることで、どれだけそれぞれの内容が近いかを数値化できます。ベクトル化すると、例えば「猫」について説明した文章があったとして、猫に近い「動物」や「トラ」といった言葉では距離が近いと判定されます。それにより、類似文書検索ができます。転置インデックスを使った全文検索だと「猫」で検索しないとヒットしませんが、ベクトル検索では近い内容も探せます。その分、ベクトルを計算するコーパスの質が大事で、今まではなかなか自由に簡単に使えるものがなかったのですが、生成AIのおかげでモデルを簡単にインストールしたり利用できるようになったので、簡単にベクトル検索ができる時代になりました。</p>
<p>まず、ollamaをインストールして、今回のembeddingで使うモデルを使えるようにしてローカルサーバーを起動しておきます。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line">ollama pull nomic-embed-text</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">ollama serve</span></span><br></pre></td></tr></table></figure>

<p>今回実験で使ったnomic-embed-textという軽量なモデルを使うと、768次元のベクトルとしてベクトルが計算できます。SIMDがなかったら、ループを回してそれぞれの項の積をしてから和を計算します。SIMDを使う場合、128ビットだと16ビットの要素の積を8つ同時に行えますし、内積用のメソッドを使うと和も求められます。</p>
<p>ベンチマークを取ってみると、6.6倍ほど高速になっていることがわかります。効果バツグンですね。なお、AMD64しか対応していないので、M3のMacBook AirでAMD64ビルドしたものをRosetta2でテストしています。そのうち早くネイティブ対応して欲しいですね。</p>
<p>なお、Rosettaのエミュレーションが公式には128ビットまでということでその範囲のテストになっていますが、非公式に256ビットとかも行けます。ただ、実行してみると128ビットの2倍程度の時間がかかっているので128ビット演算器で256ビットのエミュレーションをしている感じでしょうか？</p>
<div class="code-block"><figure class="highlight txt"><input type="checkbox" id="code-wrap-gnph5i-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-gnph5i-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">% GOARCH=amd64 GOEXPERIMENT=simd go test -bench . -benchmem</span><br><span class="line">goos: darwin</span><br><span class="line">goarch: amd64</span><br><span class="line">pkg: simdtest</span><br><span class="line">cpu: VirtualApple @ 2.50GHz</span><br><span class="line">BenchmarkPrimitive_RealData-8            5915974               203.0 ns/op             0 B/op          0 allocs/op</span><br><span class="line">BenchmarkSIMD_RealData-8                39017252                29.33 ns/op            0 B/op          0 allocs/op</span><br></pre></td></tr></table></figure></div>

<p>以下のコードがテストです。データの読み込みは省略しています。SIMDの方は[8]int16などの固定長配列を前提としているので、[][8]int16という形で、ベクトルを計算単位でまとめた二重配列的な構造にしてから与えています。なお、テストデータはReal World HTTPの原稿で、「クッキーのセキュリティ」という検索用語をそれぞれベクトル化してあらかじめファイルにしてあり、それを利用しています。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-gnph5i-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-gnph5i-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">&quot;encoding/json&quot;</span></span><br><span class="line"> <span class="string">&quot;log&quot;</span></span><br><span class="line"> <span class="string">&quot;os&quot;</span></span><br><span class="line"> <span class="string">&quot;simd/archsimd&quot;</span></span><br><span class="line"> <span class="string">&quot;testing&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// プリミティブな内積計算ロジック</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">dotProductPrimitive</span><span class="params">(a, b []<span class="type">int16</span>)</span></span> <span class="type">int32</span> &#123;</span><br><span class="line"> <span class="keyword">var</span> sum <span class="type">int32</span> = <span class="number">0</span></span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(a); i++ &#123;</span><br><span class="line">  sum += <span class="type">int32</span>(a[i]) * <span class="type">int32</span>(b[i])</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> sum</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// SIMDの内積計算ロジック</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">dotProductSIMD</span><span class="params">(a, b [][8]<span class="type">int16</span>)</span></span> <span class="type">int32</span> &#123;</span><br><span class="line"> acc := archsimd.Int32x4&#123;&#125;</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(a); i++ &#123;</span><br><span class="line">  vq := archsimd.LoadInt16x8(&amp;a[i])</span><br><span class="line">  vt := archsimd.LoadInt16x8(&amp;b[i])</span><br><span class="line">  dot := vq.DotProductPairs(vt)</span><br><span class="line">  acc = acc.Add(dot)</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> acc.GetElem(<span class="number">0</span>) + acc.GetElem(<span class="number">1</span>) + acc.GetElem(<span class="number">2</span>) + acc.GetElem(<span class="number">3</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ロード済みのデータ(初期化は省略)</span></span><br><span class="line"><span class="keyword">var</span> (</span><br><span class="line"> testQuery8     []<span class="type">int8</span></span><br><span class="line"> testQuery16    []<span class="type">int16</span></span><br><span class="line"> testQuerySIMD  [][<span class="number">8</span>]<span class="type">int16</span></span><br><span class="line"> testTarget16   []<span class="type">int16</span></span><br><span class="line"> testTargetSIMD [][<span class="number">8</span>]<span class="type">int16</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// --- ベンチマーク関数 ---</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkPrimitive_RealData</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line"> b.ResetTimer()</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; b.N; i++ &#123;</span><br><span class="line">  _ = dotProductPrimitive(testQuery16, testTarget16)</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkSIMD_RealData</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line"> b.ResetTimer()</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; b.N; i++ &#123;</span><br><span class="line">  _ = dotProductSIMD(testQuerySIMD, testTargetSIMD)</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h2 id="ベクトルの作成">ベクトルの作成</h2><p>ローカルでollamaを動かして、それにファイルを投げてベクトル化をします。なお、ベクトル化する場合はあまり長すぎるデータを与えてもコンテキストから溢れてエラーになってしまうため、あらかじめ原稿データを小さい単位の.rstファイルに分割しておきました。だいたい節タイトルごとに10行とか20行以下ぐらいに区切り、節タイトルがファイル名となるように事前処理しました。</p>
<p>積をSIMDでやるために計算はint16でやっていますが、データ自体は8ビットのベクトルとしていました。このあたり、どのあたりが良いのかはチューニングしていきたいですね。</p>
<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-gnph5i-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-gnph5i-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">&quot;context&quot;</span></span><br><span class="line"> <span class="string">&quot;encoding/json&quot;</span></span><br><span class="line"> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"> <span class="string">&quot;log&quot;</span></span><br><span class="line"> <span class="string">&quot;os&quot;</span></span><br><span class="line"> <span class="string">&quot;path/filepath&quot;</span></span><br><span class="line"></span><br><span class="line"> <span class="string">&quot;github.com/ollama/ollama/api&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 保存用のデータ構造</span></span><br><span class="line"><span class="comment">// key: ファイル名, value: int8のベクトル</span></span><br><span class="line"><span class="keyword">type</span> VectorStore <span class="keyword">map</span>[<span class="type">string</span>][]<span class="type">int8</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(os.Args) &lt; <span class="number">2</span> &#123;</span><br><span class="line">  fmt.Println(<span class="string">&quot;使用法: go run main.go &lt;directory_or_file&gt;&quot;</span>)</span><br><span class="line">  <span class="keyword">return</span></span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> inputPath := os.Args[<span class="number">1</span>]</span><br><span class="line"> client, _ := api.ClientFromEnvironment()</span><br><span class="line"> ctx := context.Background()</span><br><span class="line"> model := <span class="string">&quot;nomic-embed-text&quot;</span></span><br><span class="line"> store := <span class="built_in">make</span>(VectorStore)</span><br><span class="line"> files, _ := filepath.Glob(filepath.Join(inputPath, <span class="string">&quot;*.rst&quot;</span>))</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> _, file := <span class="keyword">range</span> files &#123;</span><br><span class="line">  content, err := os.ReadFile(file)</span><br><span class="line">  <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">   <span class="keyword">continue</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 1. Ollamaから float32 ベクトルを取得</span></span><br><span class="line">  req := &amp;api.EmbedRequest&#123;Model: model, Input: <span class="type">string</span>(content)&#125;</span><br><span class="line">  resp, err := client.Embed(ctx, req)</span><br><span class="line">  <span class="keyword">if</span> err != <span class="literal">nil</span> || <span class="built_in">len</span>(resp.Embeddings) == <span class="number">0</span> &#123;</span><br><span class="line">   log.Printf(<span class="string">&quot;Skip %s: %v&quot;</span>, file, err)</span><br><span class="line">   <span class="keyword">continue</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. float32 -&gt; int8 量子化</span></span><br><span class="line">  floatVec := resp.Embeddings[<span class="number">0</span>]</span><br><span class="line">  int8Vec := <span class="built_in">make</span>([]<span class="type">int8</span>, <span class="built_in">len</span>(floatVec))</span><br><span class="line">  <span class="keyword">for</span> i, v := <span class="keyword">range</span> floatVec &#123;</span><br><span class="line">   val := v * <span class="number">127.0</span></span><br><span class="line">   <span class="keyword">if</span> val &gt; <span class="number">127</span> &#123;</span><br><span class="line">    val = <span class="number">127</span></span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">if</span> val &lt; <span class="number">-128</span> &#123;</span><br><span class="line">    val = <span class="number">-128</span></span><br><span class="line">   &#125;</span><br><span class="line">   int8Vec[i] = <span class="type">int8</span>(val)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  shortName := filepath.Base(file)</span><br><span class="line">  store[shortName] = int8Vec</span><br><span class="line">  fmt.Printf(<span class="string">&quot;Vectorized: %s\n&quot;</span>, shortName)</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 3. JSONファイルとして書き出し</span></span><br><span class="line"> outputFile := <span class="string">&quot;vectors.json&quot;</span></span><br><span class="line"> jsonData, _ := json.MarshalIndent(store, <span class="string">&quot;&quot;</span>, <span class="string">&quot;  &quot;</span>)</span><br><span class="line"> os.WriteFile(outputFile, jsonData, <span class="number">0644</span>)</span><br><span class="line"></span><br><span class="line"> fmt.Printf(<span class="string">&quot;\nSuccess! Saved to %s\n&quot;</span>, outputFile)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>検索の方のプログラムは全文を載せませんが、すでに載せているロジックの組み合わせでいきます。検索用語の方も上記のベクトル化と同じようにベクトル化を行います。そして、[][8]int16にすべてベクトルを変換しておき、あとはドット積の大きい順序でソートして返すだけです。</p>
<h2 id="1-29追記-コンパイラの内部処理">1&#x2F;29追記: コンパイラの内部処理</h2><p>ドキュメントを見ると、それぞれのメソッドに該当するCPU命令が書かれています。インテルのC言語のリファレンスもほぼ同じなのですが、1つの関数が1つのアセンブラ命令に該当します。このような関数をIntrinsicsと呼びます。Goの処理系でも、<code>runtime</code>や<code>sync</code>パッケージなどを中心に以前からこのようなintrinsicsな関数が多数存在していました。今回のSIMDのメソッドも同様の手法で実装されています。Go 1.7の時代にSSA形式が導入されましたが、それの結果、このような最適化も実装できるようになったようですね。</p>
<h2 id="まとめ">まとめ</h2><p>AI界隈でよくでてくる計算がGoでも簡単に実現できるようになりました。類似検索とかベクトルデータベースの実装が今後増えることを期待しています。対応アーキテクチャも今後増えていくでしょう。WASMも対応になると嬉しいですね。</p>
<p>明日は武田さんになります。</p>
]]></content>
    <summary type="html">Go 1.26のリリースの足音が聞こえてきたので1.26の新機能のうち、気になる機能を有志で紹介していく連載を行います。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="Go1.26" scheme="https://future-architect.github.io/tags/Go1-26/"/>
    <category term="SIMD" scheme="https://future-architect.github.io/tags/SIMD/"/>
    <category term="インデックス" scheme="https://future-architect.github.io/tags/%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9/"/>
  </entry>
  <entry>
    <title>Software Design 2026年1月号のアルゴリズム特集に寄稿したお話</title>
    <link href="https://future-architect.github.io/articles/20251218a/"/>
    <id>https://future-architect.github.io/articles/20251218a/</id>
    <published>2025-12-17T15:00:00.000Z</published>
    <updated>2025-12-17T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[
<img fetchpriority="high" src="/images/2025/20251218a/TH800_642601.jpg" alt="" width="500" height="706">


<p>真野さん、松本さんと一緒にSoftware Designの特集記事に寄稿しました。</p>
<p>当初は3章構成の2章だけで、「配列 vs 連結リスト &#x2F; HashMap vs Tree」みたいな企画内容として話が来ました。しかし、企画を見たときに「今どき連結リストとかわざわざ作ったり、古典的なアルゴリズムとデータ構造の話ってどこまで役に立つんだろうか？」みたいな、自分で何か設計するときに企画でいただいたような内容ってあんまり考慮することはないかな、ということを思い、オタク特有の早口でチャットに以下のようなことを書きました（一部読みにくかったので原文ママではない）</p>
<blockquote>
<p>アルゴリズム選定。圧縮をJSで自前実装しないでネイティブで実装されたブラウザAPIを使おうとか、そういう方が実用的な気はしますね。木構造自前実装することなんてなさそう。今時は複雑なコードと速度がトレードオフになることはなさそう。</p>
<p>データ構造はどちらかというと機能性で選ぶことのほうが多そう</p>
<p>いまどきはよっぽど変なコーディングしないとCPUとかメモリで困ることってない。DBとか、通信の非同期かとか最適化とか、スクリプト言語はネイティブ言語のライブラリとかハードウェアアクセラレーションが効くようにしましょう、とかそっちの方が100倍効く。フロントエンドだと画面更新時の影響範囲が広くならないように・・・とかかな</p>
<p>これに関数型のエッセンスでもまぶしてあげれば良さそう</p>
</blockquote>
<p>その結果、2章に引き続き3章も当社で、また内容も大幅変更ということになり、今回の記事となりました。</p>
<h2 id="なぜこのような思いを持つようになったか">なぜこのような思いを持つようになったか</h2><p>アルゴリズム的には高速とされる圧縮アルゴリズムLZ4に興味をもって実装したことがありました。ブラウザ上ではそのままは使えず、JSに移植してみたがそんなに早くなかった、というのが最初の原体験ですかね。実装言語とかそっちの方が影響が大きいと。当時のiPhone 3GSとか4の時代、WASMもその元になったasm.jsも出る前ですね。もちろん、実装言語とかが選べない場合に、限られた選択肢の中でベストを尽くすみたいなケースはあります。</p>
<p>また、当時はZStandardが出る直前ぐらいの時代で、いろんな圧縮アルゴリズム、画像圧縮アルゴリズムとかをどのように組み込むかみたいなことをしていましたが、このレベルまで来ると基本的にゼロからのアルゴリズムの自作は難しくなってきます。コンピュータサイエンスのすごく深い知識とかが前提になりますし、今から既存の圧縮アルゴリズムを超えるようなものを作れたとしたら即論文になるレベル。</p>
<p>そのようになってくると、だんだん組み合わせの工夫、みたいな話になってきます。以下のQiitaで書いた記事とかもそれですね。</p>
<ul>
<li>Zipの内部で使うアルゴリズムを独自のものに切り替える</li>
</ul>
<p>それ以外にはPNG画像生成時に圧縮アルゴリズムを取り換えて高圧縮を狙うとかは僕もやってみました。Zopfliを使って互換性を保ったまま高圧縮にするとか、Brotliを使って互換性がなくてもいいからさらに頑張るとか。モバイルゲームだとGPUに読み込ませるときは無圧縮画像で渡すので、途中の互換性は不要だったりするので、そういう工夫もでき、いろいろ自由に試行錯誤できたのはよい経験になりました。あとは既存のアルゴリズムでも圧縮しやすいように可逆のPNGでもあえてディザリングで色数を落として16bitの色数に絞る（GPUにも16bitモードがあるので実環境上でも省メモリ）とか。</p>
<ul>
<li>https://github.com/shibukawa/lightpng</li>
</ul>
<p>僕が良く使うたとえでいえば、ファッションというものが、自分で洋服を作るのではなく服の選び方にシフトしていく、みたいな時代の変遷ですね。ファッション全然わかんないですが。</p>
<p>もう1つ、二分探索とかTimSortとか頑張ってみたけど早くなかった、というのも経験としてありました。Goにジェネレータが来る前に、C++のSTLのように使えるコードジェネレータを作ってみたことがありました。アルゴリズムの本の通りにそのまま実装するだけでは実用的なものを作るのは難しいです。ただ、作ってみたこと自体は無駄ではなかったな、と思っています。</p>
<ul>
<li>https://github.com/shibukawa/slices</li>
</ul>
<p>昔、本を読んで感動したBM法というアルゴリズムも、誰から聞いたか忘れましたが「今どきは使わないのだよ。SIMDでやっちゃうし」みたいな話も、自分には影響がありましたね。</p>
<h2 id="データ構造について考えた">データ構造について考えた</h2><p>そんな時代にあって、アルゴリズムとデータ構造に求められるものはなんだろうか？というのが最初のチャットの話になります。データ構造とアルゴリズムの本を見ると、データのコレクションとソートみたいな話が多いです。動的計画法みたいなものもありますが、今回はそこは割愛しました。で、現代の言語だと最初からデータ構造が提供されているし、検索のメソッドも最初からついていることが当たり前です。原稿にも書きましたが、C言語とかC++の1990年代は当たり前ではなかったのですが、今は「どうやって検索しよう」と悩むこととかは減ったのでは、と思います。一方で、ウェブフロントエンドでは、イミュータブルという話が最近多く出てきますね。そのあたりを考えて2章の前半の僕のパートは構成しています。</p>
<ul>
<li>配列、ソート済み配列、辞書</li>
<li>データ構造のバリエーション</li>
<li>標準のデータ構造を扱う性能以外のメリット</li>
<li>データ構造の理解がアドバンテージを産んだ時代</li>
<li>現代のデータ構造に求められる不変性</li>
<li>なぜデータ構造やアルゴリズムの自作が難しくなったのか</li>
<li>O記法の落とし穴</li>
<li>組み込みの配列や辞書の応用</li>
<li>データ構造のようなアルゴリズム</li>
<li>それでもデータ構造が大事なケース</li>
</ul>
<p>まずは言語に組み込みのデータ構造をきちんと理解して使いましょう、という内容にしています。Go&#x2F;JavaScript&#x2F;Pythonの紹介をしています。当初はJavaとC++もありましたが。</p>
<p>1章は別の会社の方が書かれていたので内容は確認できませんでしたが、おそらくO記法とかの話は来るだろうと（そして来た）思い、それを踏まえた構成にしています。前節で書いた内容にプラスして、ジェネレータのような疑似データ構造のようなものとか、関数型の話をプラスして構成しています。</p>
<h2 id="ぜひお手に取っていただけると">ぜひお手に取っていただけると</h2><p>松本さんが書いたDuckDBの話も、本当はもっといろいろ興味深い話もありましたが紙面の都合でちょっと割愛されちゃっていたりしますが、それでも結構興味深い内容になっています。真野さんの3章も、アーキテクチャの失敗はアルゴリズムで覆せないぞ、という辛口な内容でネットワークからデータベースから幅広い話が入っていて、かなり実用的な内容となっています。</p>
<p>なかなかいい特集になったと思います。是非お近くの書店にダッシュしてお手に取っていただければと思います。</p>
]]></content>
    <summary type="html">真野さん、松本さんと一緒にSoftware Designの特集記事に寄稿しました。当初は3章構成の2章だけで、「配列 vs 連結リスト / HashMap vs Tree」みたいな企画内容として話が来たのですが、企画を見たときに...</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="SoftwareDesign" scheme="https://future-architect.github.io/tags/SoftwareDesign/"/>
    <category term="アルゴリズム" scheme="https://future-architect.github.io/tags/%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0/"/>
    <category term="出版" scheme="https://future-architect.github.io/tags/%E5%87%BA%E7%89%88/"/>
  </entry>
  <entry>
    <title>なぜアーキチームは設計や実装のパターンを絞りたいか？ 背景にある思考と技術選定のジレンマ</title>
    <link href="https://future-architect.github.io/articles/20251107a/"/>
    <id>https://future-architect.github.io/articles/20251107a/</id>
    <published>2025-11-06T15:00:00.000Z</published>
    <updated>2025-11-06T15:00:00.000Z</updated>
    <author><name>真野隼記</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20251107a/unnamed.jpg" alt="" width="1024" height="1024">

<p>秋のブログ週間の5本目です。TIG 真野です。</p>
<p>アーキチーム（アーキテクト）やテックリードなどからの設計&#x2F;開発のレビューで、こんな経験はありませんか？</p>
<ul>
<li>「その実装は、この機能ID “BL310” の実装パターンでお願いします」</li>
<li>「そのライブラリの採用は見送りでお願いします」</li>
<li>「そのミドル（DB）のその機能の利用は原則禁止です。え？ はい、あー、、ガイドラインには…確かに今、書いていないですね。後出しで申し訳ないのですが利用はしない方向でお願いします。開発規約には私がこれから追記しますね。え？ あ、はい。そうですね、今度タイ料理でも食べに行きましょう」</li>
</ul>
<p>難しい要件・厳しい期限・不確実性・そして必ずしも満足とは言い難い体制の中、こっちはベストを尽くしていて、ましてより直感的で意図が明確な設計や実装だと思っていて、ていうか品質やスケジュールで遅延が生じた際に結局、矢面に立つのは自分なのに、否定されてかつやり直す合理性がどこにあるの？ 仮に指摘事項が”正”だとしても、それをやるのって今なの？ 今って割とけっこう瀬戸際なんだけどと、感覚が鈍ってるんじゃないの？…とモヤったことがある人は多いのでないでしょうか。</p>
<p>私は最近の比率で言うとモヤッとさせている側です。すいません。そしてメンバーから「なんでダメなんでしたっけ？」と言われ説明することもしばしばです。秋の夜長を楽しむという秋のブログ週間にあやかって、どういう思考でそういう発言をしているのか説明します。</p>
<h2 id="実装-設計パターンを絞りたい理由は全体最適と局所最適">実装&#x2F;設計パターンを絞りたい理由は全体最適と局所最適</h2><p>導入で触れた「モヤッと」の多くは、レビュイーが見ている「局所最適（目の前のタスクの最速完了）」と、レビュワーが見ている「全体最適（プロジェクト全体の長期的な健全性）」の視点の違いで、大まかに説明できる気がします。</p>
<p>開発言語・フレームワーク・マネージドサービス、バージョンアップのたびに、次々と便利な機能が追加されます。若手開発者目線ではそういった新機能を享受したいのはキャリア観点でもタスク遂行の観点でごもっともです。それでも新しいパターンを増やしたくない場合、私は以下の考えが多いです。</p>
<ul>
<li><strong>コードベース全体の一貫性を保ち、認知負荷を小さくする。予測可能性を保ちたい</strong><ul>
<li>新しいメンバーが参画して来たときの学習コスト（引き継ぎコスト）を下げたい</li>
<li>将来的に横断的な修正作業が発生した時に、調査漏れするリスクを軽減したい</li>
</ul>
</li>
<li><strong>各開発者への設計余地を減らし均質化したい</strong><ul>
<li>同じこと（例えばHTTPアクセスなど）をするのに、手法Aと手法Bが存在すると、開発者がどちらを使うべきか迷います。空気を読んで選んでねと言いたいところですが、レビューの場でひっくり返されるとお互い嫌です。何かしら指針を開発ガイドラインへ追記が必要です。そうすると、既存のコードがその開発指針に従っているか確認し、従っていない場合は修正しないと矛盾が生じます。そこまでしてやること？</li>
<li>より良い手法を編み出せる開発者は良いのですが、チームメンバーの成熟度によっては必ずしも正しい選択ができるメンバーばかりとは限りません。手法Bを本来使うべきでないところに、手法Bを導入してしまう場合もしばしば見受けられます。選択肢が増えるとできる開発者は嬉しいですが、そうでない場合、開発難易度が上がるということでもあります。難しい案件であればあるほど、プロジェクトの遅延リスクとなる要因は、万難を排しておきたい気持ちの場合があります</li>
</ul>
</li>
<li><strong>ライブラリや利用サービスの追加による管理コストを下げたい</strong><ul>
<li>ライブラリやクラウドサービスは便利なのでどんどん新しく使用したくなります。システム構成図や技術スタック図にアイコンが沢山並んでいると、なんかイケているPJに所属している感じが嬉しいです。しかし、それぞれ学習コストが発生しますし、バージョンアップコストや脆弱性対応など追加作業が発生します。引き継ぎ先にそのサービスやライブラリを使ったことがないので、詳しい使い方を伝授して欲しいと言われる可能性もあります。ドキュメント作成の手間を増やしてでも本当にやるべきだったのでしょうか</li>
</ul>
</li>
</ul>
<p>つまり、各機能単位でより良い実装方法が存在したとしても、システム全体でパターンを増やすデメリットを上回るメリットが存在するのかを考えています。特に保守運用時の観点は重要です。プロジェクトのライフサイクルによっては、今の熟練メンバーがいなくなりよりジュニアなメンバーで運用する場面が存在するかもしれません。その時に、難しい調査・改修が難しいコードベースになっていないか、引き継ぎしやすい構成になっているかは、軽視したくありません。</p>
<h2 id="象牙の塔">象牙の塔</h2><p>もちろん、統制が常に正解と思い込み、現場の状況を無視した開発規約を強制するといった「象牙の塔」的なアンチパターンも世の中にはあると聞いたことがあります。統制を強めること自体にも当然デメリットがあります。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">メリット</th>
<th align="left">デメリット</th>
</tr>
</thead>
<tbody><tr>
<td align="left">✅ 品質の担保</td>
<td align="left">🚫 モチベーション低下（特に若手）</td>
</tr>
<tr>
<td align="left">✅ 認知負荷の低減</td>
<td align="left">🚫 開発速度の低下</td>
</tr>
<tr>
<td align="left">✅ 保守性の向上</td>
<td align="left">🚫 変化への対応の遅れ</td>
</tr>
</tbody></table></div>
<p>統制と裁量の最も良いバランスの、スイートスポットは常に状況で変化します。現場をよく観察した上で考え続けて柔軟に意思決定していく必要があります。そのため、フェーズの前半と後半で、「あ、自分違うことを言っているな」と思う時があります。その場合は何かしらの前提が変わったことから、比重を変えたくなったということですので、疑問に思ったら「なんの要因で方針が変化したの？」と聞いてみると良いと思います。アーキテクト的にも自分の考えを言語化できてきっと嬉しいでしょう。</p>
<p>私の経験上では、プロジェクト初期は不確実性が高く、開発を軌道に載せて魅力的な機能実装をスピーディに行うことでユーザー獲得を目指すフェーズでは自由度を高めていました。その後、ユーザーが増え安定性を求められるようになってきた段階で、保守運用を見据えてレビューで細かく処理方式を指摘するようになった、ということがあります。</p>
<h2 id="自律と統制のバランス。開発速度が最優先の場合">自律と統制のバランス。開発速度が最優先の場合</h2><p>先程の象牙の塔の話のように、必ずしも、アーキチームは統制を強めたいわけではなく、状況によります。</p>
<p>例えば、新規事業のプロダクト開発をする場合は、今目の前で市場（顧客）が求める機能を提供しないと、プロジェクトが即終了するとか、開発したものが来週には不要になることもザラという、痺れる状況もあります。</p>
<p>この様な場合は、開発速度が最優先のため、さきほどいった実装&#x2F;設計パターンを絞るという方向にはならないでしょう。必ず守るべき、セキュリティやコンプライアンス対応のみしっかりチェックし、あとは腕利きの開発者のセンスに任せることが最適解になります。</p>
<p>ある案件ではメチャクチャ自由度高く任せてくれたアーキテクトが、別の案件ではめちゃくちゃ細かいところまで口出しするようになったというのは、プロジェクト特性の差がおそらく理由の大きな部分を占めます。</p>
<h2 id="開発ガイドラインの功罪">開発ガイドラインの功罪</h2><p>導入の例（「ガイドラインには…確かに今、書いていないですね」）で触れられている、レビュワー側の「後出し」問題について言及します。基本的にガイドライン整備は後手に回ります。いや、最初から書いておけよってのは分かるのですが、そこまで工数を割けないこともありますし、その他の理由もあります。</p>
<ul>
<li>ガイドラインをしっかり書いても、どうせ読まれない問題<ul>
<li>一番読んで欲しい人ほど、読まないものです。費用対効果を考えるとここばかりに注力が難しい</li>
</ul>
</li>
<li>書けば書くほど、読まれなくなる<ul>
<li>量が多いと、必然的に読むのが難しくなりますし覚えきれません</li>
</ul>
</li>
<li>最小限のルールセットで運用したい<ul>
<li>記載量が多いと、メンテナンスも大変です</li>
</ul>
</li>
</ul>
<p>緻密で厳格すぎると実効性がなくなるため、実際にレビューの場などで出てきた現場感が強い内容のみにガイドライン内容を記載したいという気持ちになります。</p>
<p>一方でルールに従う立場からすると、後出しジャンケン的になるのが難しいところです。もちろん、最初から重厚にガイドラインを書く場合もあります。例えば、プロジェクトの特性上、後出しジャンケンを繰り返すとチームの信頼関係が著しく悪化すると想定できる場合などは、初期コストをかけてでもルールを明文化することを選択します。</p>
<h2 id="静的解析・AIレビュー">静的解析・AIレビュー</h2><p>開発ガイドラインは実効性が低いので、フォーマッターやリンターの整備は必須です。カスタムルールを適用するツールを開発することも多々あります。</p>
<p>AIレビューをそれなりの規模で実践導入した経験が私にはないのですが、開発ガイドラインに記載した内容は全てAIレビューでもチェックさせるべきでしょう。もし、精度が高いのであれば、さきほど書いたガイドラインにあまり書きすぎない方が良い、という前提は覆り、バンバン追加していきましょうに変化すると思います。</p>
<h2 id="どこから、いつから、統制を強めるのか">どこから、いつから、統制を強めるのか</h2><p>基本的には、大規模になればなるほど、例外を許さず一貫性のある設計や実装を求めたくなります。各開発者の選択肢をできる限り減らし、設計の揺れを無くし均質化する戦略を指します。メンバー数が多いと、正しくない設計を選択される確率が高まるためです。また、誤った場合に手戻りを許容できる余地が無いことも多く、防御的にならざるを得ません（小規模であれば1人が2,3時間の修正で済んだとしても、20人いればかなりの工数です）。<br>一方で、小規模・準委任・アジャイルなどの案件だとむしろ開発者の能力を最大限に引き出すアプローチを取ります。</p>
<p>具体的にどのあたりでこの感覚が変わるのか考えてみます。</p>
<h3 id="チーム規模">チーム規模</h3><p>小規模なチーム（例: 5人程度）であれば、全員が顔見知りで、阿吽の呼吸で進められます。10-15名を超えてくると統制が必要だなと感じます。</p>
<p>ここで目安となるのが、人類学的な「<strong>ダンバー数</strong>」という概念があります。これは、人間が安定的な社会関係を維持できる人数の認知的な上限を示すものです。</p>
<ul>
<li><strong>〜15人:</strong> お互いを知れるので、暗黙知で統制可能です。ルールは最小限でOK。業界的には、2ピザチーム（5〜10人程度）の方が通りは良いと思います</li>
<li><strong>15〜50人:</strong> それなりに専門家をアサインした統制が必要</li>
<li><strong>50〜150人:</strong> 立ち話などでは情報が行き渡らなくなる。強いトップダウンのルールが不可欠</li>
</ul>
<p>なんとなく、「規模」をX軸、「統制レベル」をY軸とすると、なだらかな直線ではなく、特定のダンバー数の閾値を超えるあたりでS字カーブ的に統制を強めるイメージがあります。</p>
<img src="/images/2025/20251107a/scurbe.drawio.png" alt="scurbe.drawio.png" width="680" height="511" loading="lazy">

<p>理由を何となく説明すると、以下の2点のバランスがあるためです。</p>
<ol>
<li>人が増えれば増えるほど、バラツキを補正するコストは指数関数的に増えるため、ルールを強めるメリットがある</li>
<li>ルールを維持するコストは、初期は作る方も守る方も、大変ですが一度作れば比較的安定する。開発量が少なければ学習コストが見合わない</li>
</ol>
<h3 id="開発チームや引き継ぎ先の技術レベル">開発チームや引き継ぎ先の技術レベル</h3><p>小規模チームでも、ジュニアメンバーが多ければ統制は強めるべきと判断する場合があります。規模とスキルで無理やり4象限に分類してみます。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left"></th>
<th align="left">ジュニア中心</th>
<th align="left">熟練メンバー多い</th>
</tr>
</thead>
<tbody><tr>
<td align="left">小規模</td>
<td align="left">② 指導・規範</td>
<td align="left">①お任せ or 見守りモード</td>
</tr>
<tr>
<td align="left">大規模</td>
<td align="left">④ 統制を強める</td>
<td align="left">③幻想（維持困難）</td>
</tr>
</tbody></table></div>
<ul>
<li>①それなりのシニアが揃った小さなメンバーで完結できるのあれば、基本ドキュメントを書いてもらうなどのルールを設けて、見守りモードに移行しましょう</li>
<li>②小規模でもジュニアが多い場合は、迷わず開発を進められるような、サンプルプログラムや開発ルールに投資します。特に新規参画者へのケア・運用・引き継ぎ・別案件へ移籍時に困らないか？という視点は所属メンバーが見落としがちです</li>
<li>③は存在しません。万が一、熟練者を揃えていても、大規模であればメンバー入れ替わりは必ず発生するためジュニア・ミドルの参画は必然です</li>
<li>④問答無用でベストを尽くすべき</li>
</ul>
<p>注意として、運用時（引き継ぎがあればその時の体制）は、一般的にピーク時より体制は薄くなります。その最も弱い状態を見据えての技術選定を意識する必要があります。</p>
<h2 id="シニアがやるべきこと">シニアがやるべきこと</h2><p>アーキテクト視点では、アーキテクトロールが必要になる規模感の時点で、枯れた技術を採用しがちというジレンマがあります。社名に「フューチャー」が入っていることもあり、例えば10年経ったとしても色褪せない設計思想と、最新の技術で最高の価値を提供したいと思うものです。とはいえそれ以外にも、アーキテクトとして成長の楽しみの見出し方は様々です。</p>
<ul>
<li>プロジェクトライフサイクル全体で、継続的に開発が継続できるような、仕組み化を作ることに価値を見出す<ul>
<li>コード生成、静的解析などチェックの自動化</li>
<li>採用技術、パッケージ構成と、開発方針、教育などの実施スキルを上げる</li>
</ul>
</li>
<li>開発生産性の部分の技術セットを習得する<ul>
<li>CI&#x2F;CDパイプラインをがんばる、Git開発フローを整備、開発ツールを作るなど</li>
</ul>
</li>
</ul>
<p>施策の結果が出るのはしばらく時間がかかります。長期的な視点で、実施内容がどう効果を上げるか、施政者的な視点で観察できるのはとても楽しく、学びも別格だと思います。</p>
<p>もちろん、枯れた技術ばかりを入れようと推奨しているわけではありません。大規模になるほどリスクを潰し込める技術を採用したいというだけで、リスクを受け入れてでも利便性を享受したほうがトータルでメリットがあるプロジェクト規模や特性があれば、どんどんモダンな技術にチャレンジします。実績を詰めば大規模案件でも採用しやすくなります。大規模プロジェクトで導入され磨かれ洗練されたノウハウは、当然ながら小・中規模のプロジェクトにも還流するため、相互にメリットがあります。</p>
<h2 id="判断基準を言語化する。理由を伝える">判断基準を言語化する。理由を伝える</h2><p>レビュイーをモヤっとさせないために、特に棄却した場合は判断基準や理由を伝える必要があります。それを怠ると、レビュイーからは「象牙の塔からの押し付け」に思われてしまいます。まぁ普通に嫌われますよね。一般的なレビューという行為に共通する原則ですが、指摘とその理由（Why）をセットで伝えます。</p>
<ul>
<li>❌️「このライブラリは使用しないでください」</li>
<li>✅️「そのライブラリの採用は見送ってください。理由: 将来の脆弱性対応コストを考慮し、類似機能を持つ既存ライブラリAに統一する方針だからです」</li>
</ul>
<p>この辺は、コードレビューガイドラインに詳しく書いていますので、気になる方はぜひ。</p>
<p>また、ADR (Architecture Decision Records) で設計・開発上の重要な決定（「なぜこのDB機能を使わないのか」「なぜこのパターンに統一するのか」）は、ADRのような軽量なドキュメントに残していくと良いです。…と言いながら、実は私はADRを使ったことがなく、開発ガイドラインのようなドキュメントにまとめていくことが多いです。重要なのは、レビュー時に理由を説明するのと、詳細はこちらの開発ガイドラインの章（リンク）を参照ください」と伝えられる状況をできる限り多く作ることです。</p>
<p>導入の例のように「後出し」になってしまった場合は、まずそれを率直に謝罪します。その上で、「なぜ今このルールが必要になったか」の背景（例：チームが5人から15人に増えたから、ジュニアメンバーの参画が決まったから）を説明し、理解を求めることが重要です。</p>
<p>納得感は重要です。また、もちろん一貫した振る舞いも信頼関係の構築には不可欠です。信頼関係を欠いて良いものを作ることはできません。</p>
<h2 id="統制する側にも視座がある">統制する側にも視座がある</h2><p>ここまで、主にプロジェクト内での全体最適について話してきましたが、視座の高さも存在します。</p>
<ol>
<li>プロジェクト視点<ul>
<li>所属するプロジェクトを円滑に推進することに注力する視点です</li>
<li>隣のプロジェクトではモダンな技術を使えるが、こっちがダメな理由も、速度優先のPoCや、安定性が重要な基幹系といった特性の違いで説明できます</li>
</ul>
</li>
<li>複数プロジェクト&#x2F;部門視点<ul>
<li>同じ事業部などで、複数のプロジェクトが並行して動いている場合の視点です</li>
<li>部門内でノウハウを共有したり、メンバーの流動性を高めるため（いざという時にヘルプ的なアサインも可能にするよう）に、非差別化領域はなるべく標準パターンを寄せたいというインセンティブが働きます</li>
</ul>
</li>
<li>全社視点<ul>
<li>全社レベルで標準化することで、知見を集約しセキュリティやコンプライアンス要件を満たしやすくし、人材育成コストを最適化していきたいといった視点です</li>
<li>技術広報・採用広報といったことと連動し、ビジネス計画に沿った人材ポートフォリオも考えていく必要がでてきます</li>
</ul>
</li>
</ol>
<p>私の感覚では、この「全社視点」または「部門視点」で特に揃えておきたいのが、Webアプリケーションフレームワークや、DBアクセスライブラリ系（ORマッパーなど）、ログライブラリなど利用頻度が高いライブラリ群は揃えておきたいと思います。ちょっとした実装の違いではあるのですが、各プロジェクトがバラバラであればレビュー時も混乱しますし、先述した脆弱性の横断対応など何かと不都合の方が大きいです。とは言え、この論法では技術の新陳代謝が働かなくなるので、プロジェクトの技術選定のたびに、「部分的」にその時点で最も良いと判断できるものを取り入れていくといった戦法を取ることが多いです。チャレンジ要素をどこまで盛り込むかは、これまで述べたプロジェクト特性やメンバースキルセットにも依存します。プロジェクト視点で「なぜこのライブラリや開発方針を採用するんだ？」と思っても答えが出ない場合は、こうした視座が違うということで説明できるかもしれません。</p>
<p>だれしもがプロジェクト最適の視点（保守運用や引き継ぎまで見据えたプロジェクトライフサイクル全体）で考えるところから始めます。アーキテクトとして成長してくると、複数プロジェクトの管理を任されることも多いでしょう。最終的には全社の技術方針について、関与・責任の一端を担うようになってきます。もちろん、視座を高く持てば持つほど現場との距離は乖離するため、あくまで現場に適用できるかは常に脳内で検証すべきです。そのため、全社レベルでの視点から、今このプロジェクトがどれくらい整合があるか、あるいは乖離しているかは常に念頭に置いておく必要があります。もし、こういった視点を欠いてプロジェクト最適な行動を取りすぎると、大きな仕事は任されにくくなってしまいます。アーキテクチャに個性は必要無く、妥当で良いのです。目新しい技術を目玉に掲げる必要は時としてありますが、それは自分のキャリアの彩りのためになってはなりません。</p>
<p>全社レベルの技術標準は、よほど社歴が長いメンバーを除き、腹落ちレベルの理解はしていないと思うべきです。少なくともドキュメント化しておきアクセスしやすい状況にしておくとよいでしょう。公式ではないですが、フューチャーでは社内有志で設計ガイドラインを作っていたりします。これもその1つの方法です。</p>
<ul>
<li>https://future-architect.github.io/arch-guidelines/</li>
</ul>
<p>また、レビューで「モヤッと」する指摘を受けた時、その背景にはプロジェクト・部門・全社のどのレベルの最適化を目指した判断なのかを（タイ料理でも食べながら）聞いてみると、その統制の意図がより深く理解できるかもしれません。基本的にプロジェクト視点と全社視点は短期的には利益相反することが多いですが、長期的には分かりあえると思うので、双方心を開く方向で話し合うと良いと思います。</p>
<h2 id="まとめ">まとめ</h2><p>アーキテクトの役割を持つと、短期的な「開発速度」と長期的な「引き継ぎコスト」というジレンマの中で、いい感じの最適解で統制・裁量のバランスを取ろうとします。パターンを減らして長期的なコストを減らすか、自由度を増やして開発速度を上げるかの調整ゲームです。スイートスポットは、チームの規模や技術力にも依存します。</p>
<p>何か新しい技術や実装方法を導入したい場合、全体を見通して（場合によっては既存を修正して、開発規約を修正して、稼働済みシステムであればリリースして）までして行うべきなのかという視点を持っておくと、話がスムーズに進むかもしれません。現場の現実を無くして規則を正しく作ることはできませんが、全体最適の視点を無くして個別の話はできないため、両者の立場を尊重しつつ、建設的な議論をしていければと思います。</p>
]]></content>
    <summary type="html">アーキチーム（アーキテクト）やテックリードなどからの設計/開発のレビューで、こんな経験はありませんか？「その実装は、この機能ID BL310 の実装パターンでお願いします」</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="アーキテクチャ" scheme="https://future-architect.github.io/tags/%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3/"/>
    <category term="技術選定" scheme="https://future-architect.github.io/tags/%E6%8A%80%E8%A1%93%E9%81%B8%E5%AE%9A/"/>
    <category term="設計" scheme="https://future-architect.github.io/tags/%E8%A8%AD%E8%A8%88/"/>
  </entry>
  <entry>
    <title>JDK25のアップデート</title>
    <link href="https://future-architect.github.io/articles/20251002a/"/>
    <id>https://future-architect.github.io/articles/20251002a/</id>
    <published>2025-10-01T15:00:00.000Z</published>
    <updated>2025-10-01T15:00:00.000Z</updated>
    <author><name>岸本卓也</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20251002a/thumbnail.jpg" alt="" width="300" height="197">

<h2 id="はじめに">はじめに</h2><p>こんにちは、TIGの岸本卓也です。 Java25リリース記念ブログ連載 シリーズの記事です。</p>
<p>本稿では以下の JDK 25 の変更点の内、プレビュー以外の言語機能に関する変更点を中心に内容を見ていきます。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>JEP</th>
<th>Title</th>
<th>Category</th>
<th>Sub category</th>
</tr>
</thead>
<tbody><tr>
<td>470</td>
<td>PEM Encodings of Cryptographic Objects (Preview)</td>
<td>Preview &amp; Incubating</td>
<td>Libraries</td>
</tr>
<tr>
<td>502</td>
<td>Stable Values (Preview)</td>
<td>Preview &amp; Incubating</td>
<td>Libraries</td>
</tr>
<tr>
<td>503</td>
<td>Remove the 32-bit x86 Port</td>
<td>Removals</td>
<td>HotSpot JVM</td>
</tr>
<tr>
<td>505</td>
<td>Structured Concurrency (Fifth Preview)</td>
<td>Preview &amp; Incubating</td>
<td>Libraries</td>
</tr>
<tr>
<td>506</td>
<td>Scoped Values</td>
<td>Additions</td>
<td>Libraries</td>
</tr>
<tr>
<td>507</td>
<td>Primitive Types in Patterns, instanceof, and switch (Third Preview)</td>
<td>Preview &amp; Incubating</td>
<td>Language</td>
</tr>
<tr>
<td>508</td>
<td>Vector API (Tenth Incubator)</td>
<td>Preview &amp; Incubating</td>
<td>Libraries</td>
</tr>
<tr>
<td>509</td>
<td>JFR CPU-Time Profiling (Experimental)</td>
<td>Additions</td>
<td>HotSpot JVM</td>
</tr>
<tr>
<td>510</td>
<td>Key Derivation Function API</td>
<td>Additions</td>
<td>Libraries</td>
</tr>
<tr>
<td>511</td>
<td>Module Import Declarations</td>
<td>Additions</td>
<td>Language</td>
</tr>
<tr>
<td>512</td>
<td>Compact Source Files and Instance Main Methods</td>
<td>Additions</td>
<td>Language</td>
</tr>
<tr>
<td>513</td>
<td>Flexible Constructor Bodies</td>
<td>Additions</td>
<td>Language</td>
</tr>
<tr>
<td>514</td>
<td>Ahead-of-Time Command-Line Ergonomics</td>
<td>Additions</td>
<td>HotSpot JVM</td>
</tr>
<tr>
<td>515</td>
<td>Ahead-of-Time Method Profiling</td>
<td>Additions</td>
<td>HotSpot JVM</td>
</tr>
<tr>
<td>518</td>
<td>JFR Cooperative Sampling</td>
<td>Additions</td>
<td>HotSpot JVM</td>
</tr>
<tr>
<td>519</td>
<td>Compact Object Headers</td>
<td>Additions</td>
<td>HotSpot JVM</td>
</tr>
<tr>
<td>520</td>
<td>JFR Method Timing &amp; Tracing</td>
<td>Additions</td>
<td>HotSpot JVM</td>
</tr>
<tr>
<td>521</td>
<td>Generational Shenandoah</td>
<td>Additions</td>
<td>HotSpot JVM</td>
</tr>
</tbody></table></div>
<h2 id="503-Remove-the-32-bit-x86-Port">503: Remove the 32-bit x86 Port</h2><p>JEP 449 (JDK 21) 辺りから 32-bit x86 サポートの廃止に向けた動きが進んできていました。 Linux 向けの 32-bit x86 関連ソースが残るのみとなっていましたが、32-bit x86 サポートのために発生していた足かせの排除やビルド・テストの簡素化のために JEP 503 にてソースが削除されました。</p>
<h2 id="506-Scoped-Values">506: Scoped Values</h2><p>JEP 429 (JDK 20) からプレビュー提供されていた Scoped Values が、プレビューを終えて正式リリースされました。本機能は主に <code>ScopedValue</code> クラスにより提供され、次のような場合に役立ちます。</p>
<p>メソッドにデータを渡す方法として、メソッド引数で渡す方法があります。メソッド引数は単純で分かりやすい方法ですが、メソッド内でさらに別のメソッドを呼び出して…といった構成で実際にデータを使うのが深い階層のメソッドである場合、各メソッド呼び出しに引数を定義してデータをバケツリレーする必要があります。メソッド引数で渡す方法は、中間のメソッドではそのデータを使わないとしてもバケツリレーのために引数を定義する必要があるという点が課題です。</p>
<p>これを解決する方法として <code>ThreadLocal</code> がよく使われます。例えば、</p>
<ol>
<li>Framework がコンテキスト情報を生成する</li>
<li>そのコンテキスト情報のもとで Framework が Application を実行する</li>
<li>Application が Framework の機能を呼び出す</li>
<li>呼び出された Framework 機能がコンテキスト情報を使う</li>
</ol>
<p>といった構成において、コンテキスト情報を <code>ThreadLocal</code> で渡す実装例は以下です。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-xxexop-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-xxexop-1" title="コードの折り返しを切り替える"></label><figcaption><span>ThreadLocalExample.java</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadLocalExample</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">var</span> <span class="variable">fw</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Framework</span>(<span class="keyword">new</span> <span class="title class_">Application</span>());</span><br><span class="line">        fw.serve(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">        fw.serve(<span class="string">&quot;Future&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Framework</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;FrameworkContext&gt; CONTEXT = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Application application;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Framework</span><span class="params">(<span class="keyword">final</span> Application application)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.application = application;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">serve</span><span class="params">(<span class="keyword">final</span> String request)</span> &#123;</span><br><span class="line">        <span class="type">var</span> <span class="variable">context</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FrameworkContext</span>(request);</span><br><span class="line">        CONTEXT.set(context);</span><br><span class="line">        application.handle();</span><br><span class="line">        CONTEXT.remove();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">greet</span><span class="params">()</span> &#123;</span><br><span class="line">        IO.println(<span class="string">&quot;Hello %s!&quot;</span>.formatted(CONTEXT.get().getName()));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FrameworkContext</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">FrameworkContext</span><span class="params">(<span class="keyword">final</span> String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">this</span>.name;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Application</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handle</span><span class="params">()</span> &#123;</span><br><span class="line">        Framework.greet();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<figure class="highlight console"><table><tr><td class="code"><pre><span class="line">C:\&gt;java ThreadLocalExample.java</span><br><span class="line">Hello Java!</span><br><span class="line">Hello Future!</span><br></pre></td></tr></table></figure>

<p>この実装は期待通り動きますが、 <code>ThreadLocal</code> の方法には以下の課題があります。</p>
<ul>
<li>自由に変更できる: <code>ThreadLocal</code> にアクセスできるコードからは <code>set</code> メソッドにより自由に値が変更できます。いつ、どこから変更されるかわからない変数の管理は難しく、バグを生みがちです。</li>
<li>無制限の存続期間: <code>ThreadLocal</code> に <code>set</code> された値は、スレッドが存続する間または <code>remove</code> メソッドが呼ばれるまで保持されます。 <code>remove</code> メソッド呼び出しは忘れがちなため不必要に長期間変数が生存する可能性があり、スレッドプールを使用する場合は意図せずリークして脆弱性やメモリリークに繋がります。</li>
<li>継承が高コスト: 親スレッドのスレッドローカル変数を継承して子スレッドを作成できますが、全てのスレッドローカル変数に対する領域を割り当てる必要があるため多くのメモリが必要となる可能性があります。</li>
</ul>
<p>このような課題を解決するため、 <code>ThreadLocal</code> よりも汎用性を減らし用途を絞った新たな API が <code>ScopedValue</code> です。 <code>ScopedValue</code> により、同一スレッド内または子スレッドとの間でより安全かつ効率的にデータを共有できます。</p>
<p>先程の Framework-Application の例を <code>ScopedValue</code> に変更した実装例は以下です。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-xxexop-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-xxexop-2" title="コードの折り返しを切り替える"></label><figcaption><span>ScopedValueExample1.java</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ScopedValueExample1</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">var</span> <span class="variable">fw</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Framework</span>(<span class="keyword">new</span> <span class="title class_">Application</span>());</span><br><span class="line">        fw.serve(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">        fw.serve(<span class="string">&quot;Future&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Framework</span> &#123;</span><br><span class="line">    <span class="comment">// バインドされていない (i.e. 空の) scoped value は `newInstance` メソッドで作成する</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ScopedValue&lt;FrameworkContext&gt; CONTEXT = ScopedValue.newInstance();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Application application;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Framework</span><span class="params">(<span class="keyword">final</span> Application application)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.application = application;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">serve</span><span class="params">(<span class="keyword">final</span> String request)</span> &#123;</span><br><span class="line">        <span class="type">var</span> <span class="variable">context</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FrameworkContext</span>(request);</span><br><span class="line">        ScopedValue</span><br><span class="line">            <span class="comment">// scoped value に値をバインドする (i.e. セットする) には `where` メソッドを使う</span></span><br><span class="line">            .where(CONTEXT, context)</span><br><span class="line">            <span class="comment">// 値がバインドされた状態で実行したいコードは `run` メソッドで実行する</span></span><br><span class="line">            .run(() -&gt; application.handle());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">greet</span><span class="params">()</span> &#123;</span><br><span class="line">        IO.println(<span class="string">&quot;Hello %s!&quot;</span>.formatted(CONTEXT.get().getName()));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// FrameworkContext と Application は先の例と同じため記載省略</span></span><br></pre></td></tr></table></figure></div>

<figure class="highlight console"><table><tr><td class="code"><pre><span class="line">C:\&gt;java ScopedValueExample1.java</span><br><span class="line">Hello Java!</span><br><span class="line">Hello Future!</span><br></pre></td></tr></table></figure>

<p><code>ScopedValue</code> のスコープは以下のようにネストする (rebinding) こともできます。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-xxexop-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-xxexop-3" title="コードの折り返しを切り替える"></label><figcaption><span>ScopedValueExample2.java</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ScopedValue&lt;String&gt; NAME = ScopedValue.newInstance();</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    ScopedValue.where(NAME, <span class="string">&quot;Java&quot;</span>).run(<span class="built_in">this</span>::doSomething1);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">doSomething1</span><span class="params">()</span> &#123;</span><br><span class="line">    greet(<span class="string">&quot;doSomething1&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// `ScopedValue` のネスト</span></span><br><span class="line">    ScopedValue.where(NAME, <span class="string">&quot;Future&quot;</span>).run(<span class="built_in">this</span>::doSomething2);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ネストを抜けると scoped value は元の値に戻る</span></span><br><span class="line">    greet(<span class="string">&quot;doSomething1&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">doSomething2</span><span class="params">()</span> &#123;</span><br><span class="line">    greet(<span class="string">&quot;doSomething2&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">greet</span><span class="params">(<span class="keyword">final</span> String from)</span> &#123;</span><br><span class="line">    IO.println(<span class="string">&quot;%s: Hello %s!&quot;</span>.formatted(from, NAME.get()));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<figure class="highlight console"><table><tr><td class="code"><pre><span class="line">C:\&gt;java ScopedValueExample2.java</span><br><span class="line">doSomething1: Hello Java!</span><br><span class="line">doSomething2: Hello Future!</span><br><span class="line">doSomething1: Hello Java!</span><br></pre></td></tr></table></figure>

<p><code>ThreadLocal</code> の場合は値を変更すると変更されたままです。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-xxexop-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-xxexop-4" title="コードの折り返しを切り替える"></label><figcaption><span>ThreadLocalExample2.java</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;String&gt; NAME = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">main</span><span class="params">(<span class="keyword">final</span> String[] args)</span> &#123;</span><br><span class="line">    NAME.set(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">    doSomething1();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">doSomething1</span><span class="params">()</span> &#123;</span><br><span class="line">    greet(<span class="string">&quot;doSomething1&quot;</span>);</span><br><span class="line"></span><br><span class="line">    NAME.set(<span class="string">&quot;Future&quot;</span>);</span><br><span class="line">    doSomething2();</span><br><span class="line"></span><br><span class="line">    greet(<span class="string">&quot;doSomething1&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">doSomething2</span><span class="params">()</span> &#123;</span><br><span class="line">    greet(<span class="string">&quot;doSomething2&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">greet</span><span class="params">(<span class="keyword">final</span> String from)</span> &#123;</span><br><span class="line">    IO.println(<span class="string">&quot;%s: Hello %s!&quot;</span>.formatted(from, NAME.get()));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<figure class="highlight console"><table><tr><td class="code"><pre><span class="line">C:\&gt;java ThreadLocalExample2.java</span><br><span class="line">doSomething1: Hello Java!</span><br><span class="line">doSomething2: Hello Future!</span><br><span class="line">doSomething1: Hello Future!</span><br></pre></td></tr></table></figure>

<h2 id="511-Module-Import-Declarations">511: Module Import Declarations</h2><p>モジュール単位でインポートできる仕組みです。これまでインポートの宣言方法は single-type-import と type-import-on-demand の2種類でした。それぞれの例は以下です。</p>
<p>single-type-import</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Map;</span><br></pre></td></tr></table></figure>

<p>type-import-on-demand</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.*;</span><br></pre></td></tr></table></figure>

<p>新たに追加された module import によりモジュール単位でのインポートが可能になります。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="keyword">module</span> java.base;</span><br></pre></td></tr></table></figure>

<p>同名クラスが存在する場合はどのクラスをインポートするか曖昧になります。そのような場合は single-type-import または type-import-on-demand のインポート宣言を追加 (shadowing) して曖昧さを解決します。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-xxexop-5" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-xxexop-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="keyword">module</span> java.base;    <span class="comment">// java.util.Date が含まれる</span></span><br><span class="line"><span class="keyword">import</span> <span class="keyword">module</span> java.sql;     <span class="comment">// java.sql.Date が含まれる</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.*;         <span class="comment">// type-import-on-demand で曖昧さを解消する例</span></span><br></pre></td></tr></table></figure></div>

<p>なお、当社が公開している Java コーディング規約 もそうですが、インポート宣言は明確さを優先して single-type-import が好まれることが多いです。しかし、 JShell 使用時やお試し実装など明確さより便利さを優先できる場合には有用になりそうです。</p>
<h2 id="512-Compact-Source-Files-and-Instance-Main-Methods">512: Compact Source Files and Instance Main Methods</h2><p>おまじない&#x2F;ボイラープレート少なく Java プログラムを書き始められる仕組みです。プレビューでは simple source files と呼ばれていましたが compact source files という名称に変更して正式リリースされました。</p>
<p>これまでは例えば Hello, World! のコードは以下のように実装する必要がありました。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorld</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Hello, World!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>このコードには Hello, World! を出力するメインのコード以外に多くのコードが含まれているため、 Java 初学者を混乱させる可能性があります。</p>
<p>compact source files と instance main methods の仕組みを使うと、 Hello, World! のコードは以下にできます。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-xxexop-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-xxexop-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// class 定義を省略できる</span></span><br><span class="line"><span class="comment">// main メソッドの修飾子を省略できる</span></span><br><span class="line"><span class="comment">// main メソッドの引数を省略できる</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// `java.lang` パッケージに新たに追加された IO クラスを使う</span></span><br><span class="line">    IO.println(<span class="string">&quot;Hello, World!&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>class 定義に囲まれていないフィールドやメソッドを含むソースを compact source file と呼び、それらのフィールドやメソッドをメンバーとする暗黙のクラスが定義されているように扱われます。暗黙に定義されるクラスは名前を持たないため、 <code>new</code> 演算子でインスタンス化することや static メソッドのメソッド参照はできません。しかし、後述の仕組みで作られるインスタンスを <code>this</code> で参照できます。</p>
<p>暗黙定義されるクラスは次の仕様になります。</p>
<ul>
<li>無名パッケージに定義されたトップレベルの final クラス</li>
<li><code>Object</code> クラスを継承し、インターフェース実装は無し</li>
<li>コンストラクタは引数無しのデフォルトコンストラクタのみ存在する</li>
</ul>
<p>main メソッドは次の仕組みで選択されます。</p>
<ol>
<li>クラスに直接定義または継承で <code>String[]</code> を引数とする <code>main</code> メソッドがあればそのメソッドを選択する。</li>
<li>クラスに直接定義または継承で引数無しの <code>main</code> メソッドがあればそのメソッドを選択する。</li>
</ol>
<p>選択された <code>main</code> メソッドは次の仕組みで起動されます。</p>
<ol>
<li>選択されたメソッドが <code>static</code> ならそのメソッドを起動する。</li>
<li>選択されたメソッドがインスタンスメソッドなら、引数無しのコンストラクタを起動してインスタンスを作成してから選択されたメソッドを起動する。</li>
</ol>
<p>この簡潔な実装方法は 506: Scoped Values の実装例でもしれっと使っていました。</p>
<p>また、 compact source files では <code>java.base</code> モジュールが自動でインポートされるため、以下のようによく使うクラスもインポート宣言無しに使えます。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-xxexop-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-xxexop-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">var</span> <span class="variable">fruits</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>[] &#123; <span class="string">&quot;apple&quot;</span>, <span class="string">&quot;berry&quot;</span>, <span class="string">&quot;citrus&quot;</span> &#125;;</span><br><span class="line">    <span class="type">var</span> <span class="variable">m</span> <span class="operator">=</span> Stream.of(fruits).collect(Collectors.toMap(</span><br><span class="line">            s -&gt; s.toUpperCase().substring(<span class="number">0</span>,<span class="number">1</span>),</span><br><span class="line">            Function.identity()));</span><br><span class="line">    m.forEach((k, v) -&gt; IO.println(k + <span class="string">&quot; &quot;</span> + v));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h2 id="513-Flexible-Constructor-Bodies">513: Flexible Constructor Bodies</h2><p>コンストラクタの処理でコンストラクタ呼び出し (<code>super(...)</code> や <code>this(...)</code>) より前に文 (statement) を置けるようになりました。これにより、例えば以下のガード節のような実装が可能になります。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    A(<span class="keyword">final</span> <span class="type">int</span> value) &#123;</span><br><span class="line">        <span class="keyword">if</span> (value &lt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">super</span>(value);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>なお、コンストラクタ呼び出しより前では <code>this</code> や <code>super</code> などで作成中のインスタンスの参照はできず初期化のみできます。例えば以下はコンパイルエラーとなる実装です。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-xxexop-8" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-xxexop-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    A() &#123;</span><br><span class="line">        <span class="comment">// エラー: スーパータイプのコンストラクタの呼出し前はthisを参照できません</span></span><br><span class="line">        <span class="built_in">this</span>.i++;</span><br><span class="line">        <span class="comment">// エラー: スーパータイプのコンストラクタの呼出し前はiを参照できません</span></span><br><span class="line">        i++;</span><br><span class="line">        <span class="comment">// エラー: スーパータイプのコンストラクタの呼出し前はthisを参照できません</span></span><br><span class="line">        <span class="built_in">this</span>.hashCode();</span><br><span class="line"></span><br><span class="line">        <span class="built_in">super</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// コンストラクタ呼び出し後の参照はOK</span></span><br><span class="line">        <span class="built_in">this</span>.i++;</span><br><span class="line">        <span class="built_in">this</span>.hashCode();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>コンストラクタ呼び出しより前のインスタンスアクセスは初期化のみ可能です。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    A(<span class="keyword">final</span> <span class="type">int</span> value) &#123;</span><br><span class="line">        <span class="comment">// 初期化はできる</span></span><br><span class="line">        <span class="built_in">this</span>.i = value;</span><br><span class="line">        <span class="built_in">super</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="さいごに">さいごに</h2><p>本稿では JDK 25 の言語機能系の変更点をピックアップして紹介しました。</p>
<p>今回始めて Java のリリース内容を調査しましたが、 JDK 25 のページ やここからリンクされている各 JEP のページ、Oracle 社の Updates ページ は説明やサンプルが多くとても参考になりました。</p>
]]></content>
    <summary type="html">JDK 25 の変更点の内、プレビュー以外の言語機能に関する変更点を中心に内容を見ていきます。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="JEP" scheme="https://future-architect.github.io/tags/JEP/"/>
    <category term="Java" scheme="https://future-architect.github.io/tags/Java/"/>
    <category term="Java25" scheme="https://future-architect.github.io/tags/Java25/"/>
  </entry>
  <entry>
    <title>Java25リリース連載：Java 24におけるパフォーマンス周りのアップデート</title>
    <link href="https://future-architect.github.io/articles/20250924a/"/>
    <id>https://future-architect.github.io/articles/20250924a/</id>
    <published>2025-09-23T15:00:00.000Z</published>
    <updated>2025-09-23T15:00:00.000Z</updated>
    <author><name>武田大輝</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250924a/top.jpg" alt="" width="600" height="395">

<h2 id="はじめに">はじめに</h2><p>Java25リリース連載第 2 弾の記事です。</p>
<p>第 1 弾の記事 に引き続き本記事では Java 24 のアップデートを取り上げます。</p>
<h2 id="JEPs">JEPs</h2><p>Java 24 で対応された主要な 21 の JEP（Java Enhancement Proposals）は次の通りです。<br>Oracle の プレスリリース をベースにカテゴライズしています。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">カテゴリ</th>
<th align="left">JEP</th>
<th align="left">タイトル</th>
</tr>
</thead>
<tbody><tr>
<td align="left">言語機能</td>
<td align="left">JEP 488</td>
<td align="left">Primitive Types in Patterns, instanceof, and switch (Second Preview)</td>
</tr>
<tr>
<td align="left">言語機能</td>
<td align="left">JEP 492</td>
<td align="left">Flexible Constructor Bodies (Third Preview)</td>
</tr>
<tr>
<td align="left">言語機能</td>
<td align="left">JEP 494</td>
<td align="left">Module Import Declarations (Second Preview)</td>
</tr>
<tr>
<td align="left">言語機能</td>
<td align="left">JEP 495</td>
<td align="left">Simple Source Files and Instance Main Methods (Fourth Preview)</td>
</tr>
<tr>
<td align="left">ライブラリ</td>
<td align="left">JEP 485</td>
<td align="left">Stream Gatherers</td>
</tr>
<tr>
<td align="left">ライブラリ</td>
<td align="left">JEP 484</td>
<td align="left">Class-File API</td>
</tr>
<tr>
<td align="left">ライブラリ</td>
<td align="left">JEP 487</td>
<td align="left">Scoped Values (Fourth Preview)</td>
</tr>
<tr>
<td align="left">ライブラリ</td>
<td align="left">JEP 489</td>
<td align="left">Vector API (Ninth Incubator)</td>
</tr>
<tr>
<td align="left">ライブラリ</td>
<td align="left">JEP 499</td>
<td align="left">Structured Concurrency (Fourth Preview)</td>
</tr>
<tr>
<td align="left">セキュリティライブラリ</td>
<td align="left">JEP 478</td>
<td align="left">Key Derivation Function API (Preview)</td>
</tr>
<tr>
<td align="left">セキュリティライブラリ</td>
<td align="left">JEP 496</td>
<td align="left">Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism</td>
</tr>
<tr>
<td align="left">セキュリティライブラリ</td>
<td align="left">JEP 497</td>
<td align="left">Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm</td>
</tr>
<tr>
<td align="left">ツール</td>
<td align="left">JEP 493</td>
<td align="left">Linking Run-Time Images without JMODs</td>
</tr>
<tr>
<td align="left">パフォーマンスおよびランタイム</td>
<td align="left">JEP 450</td>
<td align="left">Compact Object Headers (Experimental)</td>
</tr>
<tr>
<td align="left">パフォーマンスおよびランタイム</td>
<td align="left">JEP 475</td>
<td align="left">Late Barrier Expansion for G1</td>
</tr>
<tr>
<td align="left">パフォーマンスおよびランタイム</td>
<td align="left">JEP 483</td>
<td align="left">Ahead-of-Time Class Loading &amp; Linking</td>
</tr>
<tr>
<td align="left">パフォーマンスおよびランタイム</td>
<td align="left">JEP 490</td>
<td align="left">ZGC: Remove the Non-Generational Mode</td>
</tr>
<tr>
<td align="left">パフォーマンスおよびランタイム</td>
<td align="left">JEP 491</td>
<td align="left">Synchronize Virtual Threads without Pinning</td>
</tr>
<tr>
<td align="left">ソースコード</td>
<td align="left">JEP 404</td>
<td align="left">Generational Shenandoah (Experimental)</td>
</tr>
<tr>
<td align="left">ソースコード</td>
<td align="left">JEP 479</td>
<td align="left">Remove the Windows 32-bit x86 Port</td>
</tr>
<tr>
<td align="left">ソースコード</td>
<td align="left">JEP 501</td>
<td align="left">Deprecate the 32-bit x86 Port for Removal</td>
</tr>
</tbody></table></div>
<p>第一弾の記事ではライブラリ系（JEP 484, JEP 485）およびセキュリティライブラリ系（JEP 478, JEP 496, JEP 497）を中心に取り上げましたが、本記事ではパフォーマンス系を中心に取り上げます。</p>
<h2 id="JEP-483-Ahead-of-Time-Class-Loading-Linking">JEP 483: Ahead-of-Time Class Loading &amp; Linking</h2><p>https://openjdk.org/jeps/483</p>
<p>クラスのローディングとリンクを起動前にキャッシュしておき、次回以降の起動を速くするしくみが追加されました。</p>
<h3 id="どのようなしくみか">どのようなしくみか</h3><p>具体的には次のような流れでキャッシュの生成、再利用を実現します。</p>
<ol>
<li><p>トレーニング実行<br>レコードモード（<code>-XX:AOTMode=record</code>）にてアプリケーションを一度起動し、どのクラスが読み込まれリンクされるかを記録します。<br>次の例では <code>app.aotconf</code> に設定を記録しています。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-wsql0i-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-wsql0i-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -<span class="built_in">cp</span> app.jar com.example.App ...</span></span><br></pre></td></tr></table></figure></div>
</li>
<li><p>キャッシュ生成<br>クリエイトモード（<code>-XX:AOTMode=create</code>）にて、トレーニングモードで記録した設定ファイルからキャッシュを生成します。<br>次の例では <code>app.aot</code> にキャッシュを記録しています。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-wsql0i-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-wsql0i-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -<span class="built_in">cp</span> app.jar</span></span><br></pre></td></tr></table></figure></div>
</li>
<li><p>キャッシュ利用<br>アプリケーション起動時には、キャッシュを指定して起動することでクラス読み込み・解析・リンクにかかる時間を削減できます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-wsql0i-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-wsql0i-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">java -XX:AOTCache=app.aot -<span class="built_in">cp</span> app.jar com.example.App ...</span></span><br></pre></td></tr></table></figure></div></li>
</ol>
<p>なお、キャッシュが存在しない場合やトレーニング時と実行時でクラスパスやモジュール構成が変わっているなど問題がある場合は、従来どおり just-in-time（必要になった時点でクラスをロードする&#x2F;リンクする）方式となります。</p>
<h3 id="どれぐらい高速化が見込めるのか">どれぐらい高速化が見込めるのか</h3><p>JEP 483 の中では Stream API を利用するプログラムでの検証結果が紹介されていました。<br>非常に短いプログラムですが、Stream API を利用することで約 600 ものクラスが読み込まれています。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-wsql0i-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-wsql0i-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.*;</span><br><span class="line"><span class="keyword">import</span> java.util.stream.*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloStream</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String ... args)</span> &#123;</span><br><span class="line">        <span class="type">var</span> <span class="variable">words</span> <span class="operator">=</span> List.of(<span class="string">&quot;hello&quot;</span>, <span class="string">&quot;fuzzy&quot;</span>, <span class="string">&quot;world&quot;</span>);</span><br><span class="line">        <span class="type">var</span> <span class="variable">greeting</span> <span class="operator">=</span> words.stream()</span><br><span class="line">            .filter(w -&gt; !w.contains(<span class="string">&quot;z&quot;</span>))</span><br><span class="line">            .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>));</span><br><span class="line">        System.out.println(greeting);  <span class="comment">// hello, world</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>このプログラムは Java 23 では 0.031 秒で実行されますが、Java 24 で AOT キャッシュを作成した結果 0.018 秒で実行され、約 42%程度の改善が見られました。<br>シンプルなプログラムでみるとそのインパクトはわずかに思えるかもしれませんが、Spring の PetClinic アプリケーションでは起動時間が 4.486 秒から 2.604 秒になったという結果も紹介されています。サーバレスなランタイムにおいては Java アプリケーションの起動速度が課題視されますが、この改善は大きな意味を持つのではないでしょうか。</p>
<h2 id="JEP-490-ZGC-Remove-the-Non-Generational-Mode">JEP 490: ZGC: Remove the Non-Generational Mode</h2><p>https://openjdk.org/jeps/474</p>
<p>ZGC において非世代別 GC（non-generational mode）が削除され、世代別 GC（generational mode）が唯一かつデフォルトのモードになりました。</p>
<p>ZGC は、Java 21（JEP479: Generational ZGC）にて世代別 GC がサポートされ、Java 23（JEP474: ZGC: Generational Mode by Default）にて世代別 GC がデフォルトになりました。</p>
<p>Java23 連携 でも紹介しましたが ZZGC や世代別 GC について、ここでも再掲して整理しておきます。</p>
<h3 id="そもそも-ZGC-とは何か">そもそも ZGC とは何か</h3><p>ZGC は、Oracle が開発したガベージコレクタ（GC）で、スケーラブルで低レイテンシ（数テラバイト級の非常に大きいヒープでもアプリケーションの最大停止時間が 10ms 程度）というのが特徴です。</p>
<p>Java 9 以降、G1GC がデフォルトの GC として使われていますが、巨大なヒープを取り扱うアプリケーションが登場してきたことなどを背景に、従来の GC と比べてモダンな GC として登場しました。（cf. ざっくりわかった気になるモダン GC 入門）</p>
<p>Java 11（JEP333）で試験的に導入され、Java 15（JEP377）にて正式リリースされ、今に至ります。</p>
<h3 id="世代別-GC-とは何か">世代別 GC とは何か</h3><p>世代別 GC 自体は特に新しい概念ではありません。<br>世代別 GC とは、ヒープ内のオブジェクトを寿命によって分類（Young 世代や Old 世代）し、ガベージコレクション（GC）の効率を向上させるしくみです。（cf. Java の GC の仕組みを整理する）</p>
<p>ZGC は、低遅延であることを最優先に、ヒープ全体のコレクションを極力並行処理で行うことを目的として、当初は世代別 GC のアプローチを採用しませんでした。<br>そこから、より効率的なメモリ管理などさらなる最適化を目指して、世代別 GC の概念を取り入れていったという形になります。</p>
<h2 id="JEP-491-Synchronize-Virtual-Threads-without-Pinning">JEP 491: Synchronize Virtual Threads without Pinning</h2><p>Java 21 で導入された Virtual Thread（仮想スレッド） は OS のスレッド（Platform Thread）とは別の、JVM 内で管理されるより軽量なスレッド実装であり大量のスレッドを効率的に管理するために設計されたしくみです。<br>この Virtual Thread においては <code>synchronized</code> を含むプログラムを実行すると、処理が OS のスレッドに固定化されてしまう問題がありましたが、本アップデートで改善された形になります。</p>
<p>本アップデートについては次の記事が大いに参考になりましたので、ここでの説明は割愛させていただきます。</p>
<p>https://chiroito.hatenablog.jp/entry/2025/03/22/124631</p>
<h2 id="おわりに">おわりに</h2><p>本記事では Java 24 のアップデートの中から、特にパフォーマンスに関わる JEP を中心に紹介しました。<br>次回は Java 25 の新機能を取り上げます。</p>
]]></content>
    <summary type="html">Java 24&amp;25 リリース連載第 2 弾の記事です。第 1 弾の記事 に引き続き本記事では Java 24 のアップデートを取り上げます。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="JEP" scheme="https://future-architect.github.io/tags/JEP/"/>
    <category term="Java" scheme="https://future-architect.github.io/tags/Java/"/>
    <category term="Java24" scheme="https://future-architect.github.io/tags/Java24/"/>
  </entry>
  <entry>
    <title>Java25リリース連載：JDK24のアップデート</title>
    <link href="https://future-architect.github.io/articles/20250922b/"/>
    <id>https://future-architect.github.io/articles/20250922b/</id>
    <published>2025-09-21T15:00:01.000Z</published>
    <updated>2025-09-21T15:00:01.000Z</updated>
    <author><name>前川喜洋</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250922b/top.jpg" alt="" width="600" height="395">

<h2 id="はじめに">はじめに</h2><p>コアテクノロジーグループの前川です。</p>
<p>Java25リリース連載 1本目の記事です。</p>
<p>今回はJDK 24でのアップデート内容から以下についてピックアップしてご紹介します。</p>
<ul>
<li>API系<ul>
<li>JEP 484: Class-File API （クラスファイルを解析、生成、変換するための標準API）</li>
<li>JEP 485: Stream Gatherers （Streamでより柔軟な中間操作を可能にするgatherメソッド）</li>
<li>JEP 478: Key Derivation Function API （鍵導出関数のためのAPI）</li>
</ul>
</li>
<li>セキュリティ系<ul>
<li>JEP 472: Prepare to Restrict the Use of JNI	（JNIの安全でない使用を制限）</li>
<li>JEP 486: Permanently Disable the Security Manager （セキュリティマネージャを恒久的に無効化）</li>
<li>JEP 496: Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism （量子コンピュータによる攻撃に耐性）</li>
<li>JEP 497: Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm （量子コンピュータによる攻撃に耐性）</li>
<li>JEP 498: Warn upon Use of Memory-Access Methods in sun.misc.Unsafe （sun.misc.Unsafeのメモリ操作メソッド使用時に警告）</li>
</ul>
</li>
</ul>
<h2 id="JEP-484-Class-File-API-（クラスファイルを解析、生成、変換するための標準API）">JEP 484: Class-File API （クラスファイルを解析、生成、変換するための標準API）</h2><p>Java バイトコードを読み込んでクラスファイルを解析、生成、変換するための機能を公式に提供するためのライブラリとしては、ASM や BCEL などが広く使われていますが、これらは JDK の一部ではなく、外部ライブラリとしてプロジェクトに組み込む必要があります。JEP 484 では、こうした外部ライブラリに依存せずに、JDK 標準でクラスファイルを解析、生成、変換するための API を提供します。 JDK 22から2回のプレビューを経て正式リリースとなりました。</p>
<p>以下は <code>ClassBuilder</code> を使用して既存のクラスのメソッド冒頭に <code>System.out.println</code> を仕込んで新クラスのclassファイルを作成するサンプルです。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-eoo6p8-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-eoo6p8-1" title="コードの折り返しを切り替える"></label><figcaption><span>ClassRenamer.java</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.lang.classfile.*;</span><br><span class="line"><span class="keyword">import</span> java.lang.constant.*;</span><br><span class="line"><span class="keyword">import</span> java.nio.file.*;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ClassRenamer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">Path</span> <span class="variable">sourcePath</span> <span class="operator">=</span> Path.of(<span class="string">&quot;MyTargetClass.class&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">newClassName</span> <span class="operator">=</span> <span class="string">&quot;MyTargetClass2&quot;</span>;</span><br><span class="line">        <span class="type">Path</span> <span class="variable">targetPath</span> <span class="operator">=</span> Path.of(newClassName + <span class="string">&quot;.class&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 元のクラスファイルを解析</span></span><br><span class="line">        <span class="type">byte</span>[] classBytes = Files.readAllBytes(sourcePath);</span><br><span class="line">        <span class="type">ClassModel</span> <span class="variable">originalModel</span> <span class="operator">=</span> ClassFile.of().parse(classBytes);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 新しいクラス名でクラスビルダーを開始し、元のクラスの全要素を新しいビルダーにコピーする</span></span><br><span class="line">        <span class="type">byte</span>[] newClassBytes = ClassFile.of().build(ClassDesc.of(newClassName), builder -&gt; &#123;</span><br><span class="line">            <span class="keyword">for</span> (ClassElement element : originalModel) &#123;</span><br><span class="line">                <span class="comment">// 要素がメソッドの場合、コードを変換して追加</span></span><br><span class="line">                <span class="keyword">if</span> (element <span class="keyword">instanceof</span> MethodModel method) &#123;</span><br><span class="line">                    <span class="comment">// withMethod で新しいメソッドを構築</span></span><br><span class="line">                    builder.withMethod(method.methodName(), method.methodType(), method.flags().flagsMask(), methodBuilder -&gt; &#123;</span><br><span class="line">                        <span class="comment">// 元のメソッドの属性（アノテーションなど）をコピー</span></span><br><span class="line">                        <span class="keyword">for</span> (MethodElement me : method) &#123;</span><br><span class="line">                            <span class="keyword">if</span> (!(me <span class="keyword">instanceof</span> CodeModel)) &#123;</span><br><span class="line">                                methodBuilder.with(me);</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">						<span class="type">boolean</span>[] inserted = &#123;<span class="literal">false</span>&#125;;</span><br><span class="line">						methodBuilder.transformCode(</span><br><span class="line">							method.code().orElseThrow(),</span><br><span class="line">							(codeBuilder, codeElement) -&gt; &#123;</span><br><span class="line">								<span class="comment">// フラグが false の場合（＝初回実行時）のみ挿入</span></span><br><span class="line">								<span class="keyword">if</span> (!inserted[<span class="number">0</span>]) &#123;</span><br><span class="line">									insertPrintStatement(codeBuilder, <span class="string">&quot;&gt;&gt; Entering method: &quot;</span> + method.methodName().stringValue());</span><br><span class="line">									inserted[<span class="number">0</span>] = <span class="literal">true</span>;</span><br><span class="line">								&#125;</span><br><span class="line">								codeBuilder.with(codeElement);</span><br><span class="line">							&#125;);</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    builder.with(element);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">// 変換後のバイトコードを新しいファイルに書き出す</span></span><br><span class="line">        Files.write(targetPath, newClassBytes);</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">&quot;クラス名を &#x27;&quot;</span> + newClassName + <span class="string">&quot;&#x27; に変更し、&#x27;&quot;</span> + targetPath + <span class="string">&quot;&#x27; に保存しました。&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ClassDesc</span> <span class="variable">SYSTEM</span> <span class="operator">=</span> ClassDesc.of(<span class="string">&quot;java.lang.System&quot;</span>);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ClassDesc</span> <span class="variable">PRINT_STREAM</span> <span class="operator">=</span> ClassDesc.of(<span class="string">&quot;java.io.PrintStream&quot;</span>);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">MethodTypeDesc</span> <span class="variable">PRINTLN_MTD</span> <span class="operator">=</span> MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_String);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">insertPrintStatement</span><span class="params">(CodeBuilder codeBuilder, String message)</span> &#123;</span><br><span class="line">        codeBuilder.getstatic(SYSTEM, <span class="string">&quot;out&quot;</span>, PRINT_STREAM)</span><br><span class="line">			.ldc(message)</span><br><span class="line">			.invokevirtual(PRINT_STREAM, <span class="string">&quot;println&quot;</span>, PRINTLN_MTD);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<figure class="highlight java"><figcaption><span>MyTargetClass.java</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyTargetClass</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Hello!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-eoo6p8-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-eoo6p8-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">javac *.java</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">java -<span class="built_in">cp</span> .  ClassRenamer</span></span><br><span class="line">クラス名を &#x27;MyTargetClass2&#x27; に変更し、&#x27;MyTargetClass2.class&#x27; に保存しました。</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">java -<span class="built_in">cp</span> .  MyTargetClass2</span></span><br><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt; Entering method: main</span></span><br><span class="line">Hello!</span><br></pre></td></tr></table></figure></div>

<p>考え方としてはASMに近いですが記法が異なっていて、ASMがビジターパターンなのに対してこちらはビルダーの多層構造になっており、 OpenRewrite 等でサクッと移行というわけにはいかなさそうです。</p>
<h2 id="JEP-485-Stream-Gatherers-（Streamでより柔軟な中間操作を可能にするgatherメソッド）">JEP 485: Stream Gatherers （Streamでより柔軟な中間操作を可能にするgatherメソッド）</h2><p>Streamパイプラインの中に、自由度の高いカスタム中間操作を組み込むことを可能にします。これにより、開発者はこれまで以上に柔軟かつ直観的にデータ処理を記述できるようになります。</p>
<p><code>Collector</code> で頑張ろうとすると一旦「Stream脳」から離れないといけなかったのがかなり軽減されます。 <code>Integrator</code> の <code>state</code> 、遂に来たかという感じですね。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-eoo6p8-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-eoo6p8-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.stream.Gatherer;</span><br><span class="line"><span class="keyword">import</span> java.util.stream.Gatherers;</span><br><span class="line"><span class="keyword">import</span> java.util.stream.Stream;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StreamSample</span> &#123;</span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">		<span class="comment">// 1から9までの数値を3つずつのリストにまとめる</span></span><br><span class="line">		List&lt;List&lt;Integer&gt;&gt; result = Stream.of(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>)</span><br><span class="line">				.gather(Gatherers.windowFixed(<span class="number">3</span>))</span><br><span class="line">				.toList();</span><br><span class="line"></span><br><span class="line">		<span class="comment">// 実行結果: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]</span></span><br><span class="line">		System.out.println(result);</span><br><span class="line"></span><br><span class="line">		<span class="comment">// 1から5までの数値を、3つずつのスライディングウィンドウでまとめる</span></span><br><span class="line">		List&lt;List&lt;Integer&gt;&gt; result2 = Stream.of(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>)</span><br><span class="line">				.gather(Gatherers.windowSliding(<span class="number">3</span>))</span><br><span class="line">				.toList();</span><br><span class="line"></span><br><span class="line">		<span class="comment">// 実行結果: [[1, 2, 3], [2, 3, 4], [3, 4, 5]]</span></span><br><span class="line">		System.out.println(result2);</span><br><span class="line"></span><br><span class="line">		<span class="comment">// 数値の累積和を計算する</span></span><br><span class="line">		List&lt;Integer&gt; result3 = Stream.of(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>)</span><br><span class="line">				.gather(Gatherers.scan(() -&gt; <span class="number">0</span>, (sum, i) -&gt; sum + i))</span><br><span class="line">				.toList();</span><br><span class="line"></span><br><span class="line">		<span class="comment">// 実行結果: [1, 3, 6, 10, 15]</span></span><br><span class="line">		System.out.println(result3);</span><br><span class="line"></span><br><span class="line">		<span class="comment">// カスタムGathererを使用</span></span><br><span class="line">		List&lt;List&lt;String&gt;&gt; result4 = Stream.of(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>, <span class="string">&quot;c&quot;</span>, <span class="string">&quot;c&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;a&quot;</span>)</span><br><span class="line">				.gather(groupConsecutive())</span><br><span class="line">				.toList();</span><br><span class="line"></span><br><span class="line">		<span class="comment">// 実行結果: [[&quot;a&quot;, &quot;a&quot;], [&quot;b&quot;], [&quot;c&quot;, &quot;c&quot;, &quot;c&quot;], [&quot;b&quot;], [&quot;a&quot;]]</span></span><br><span class="line">		System.out.println(result4);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** Stream内の連続する同じ要素をリストにグループ化するGathererを作成 */</span></span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; Gatherer&lt;T, ?, List&lt;T&gt;&gt; groupConsecutive() &#123;</span><br><span class="line">		<span class="keyword">return</span> Gatherer.ofSequential(</span><br><span class="line">				<span class="comment">// 1. Initializer: 中間状態を保持するコンテナを準備する</span></span><br><span class="line">				ArrayList::<span class="keyword">new</span>,</span><br><span class="line"></span><br><span class="line">				<span class="comment">// 2. Integrator: 各要素を処理する</span></span><br><span class="line">				(state, element, downstream) -&gt; &#123;</span><br><span class="line">					<span class="comment">// stateが空でなく、最後の要素が現在の要素と違う場合、これまでのstateをリストとして下流に流し、stateをクリアする</span></span><br><span class="line">					<span class="keyword">if</span> (!state.isEmpty() &amp;&amp; !state.getLast().equals(element)) &#123;</span><br><span class="line">						downstream.push(List.copyOf(state));</span><br><span class="line">						state.clear();</span><br><span class="line">					&#125;</span><br><span class="line">					<span class="comment">// 現在の要素をstateに追加する</span></span><br><span class="line">					state.add(element);</span><br><span class="line">					<span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">				&#125;,</span><br><span class="line"></span><br><span class="line">				<span class="comment">// 3. Finisher: 最後の要素グループを処理する</span></span><br><span class="line">				(state, downstream) -&gt; &#123;</span><br><span class="line">					<span class="comment">// Streamの最後に残った要素がstateにあれば、それを下流に流す</span></span><br><span class="line">					<span class="keyword">if</span> (!state.isEmpty()) &#123;</span><br><span class="line">						downstream.push(List.copyOf(state));</span><br><span class="line">					&#125;</span><br><span class="line">				&#125;);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h2 id="JEP-478-Key-Derivation-Function-API-（鍵導出関数のためのAPI）">JEP 478: Key Derivation Function API （鍵導出関数のためのAPI）</h2><p>ポスト量子暗号（PQC）対応の一環。</p>
<p>鍵導出関数（KDF）をひとことで言うと、 <strong>「鍵の”おおもと”（マスターキーやパスワード）から、用途に合わせて安全な”子鍵”を複数生成するための仕組み」</strong> です。これまでJavaには鍵を導出する為の統一的な標準APIが有りませんでした。この状況を改善し、標準化と相互運用性を高めてよりモダンで堅牢なアルゴリズムの利用を促進する事を目的として導入されたのが <code>javax.crypto.KDF</code> クラスです。</p>
<p>例はJEPのページからそのまま抜粋しますが以下のコードにより <code>javax.crypto.SecretKey</code> オブジェクトを生成できます。 <code>SecretKey</code> オブジェクトは従来と同様に <code>Cipher</code> クラスなどで利用できます。</p>
<div class="code-block"><figure class="highlight java"><input type="checkbox" id="code-wrap-eoo6p8-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-eoo6p8-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// Create a KDF object for the specified algorithm</span></span><br><span class="line"><span class="type">KDF</span> <span class="variable">hkdf</span> <span class="operator">=</span> KDF.getInstance(<span class="string">&quot;HKDF-SHA256&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Create an ExtractExpand parameter specification</span></span><br><span class="line"><span class="type">AlgorithmParameterSpec</span> <span class="variable">params</span> <span class="operator">=</span></span><br><span class="line">    HKDFParameterSpec.ofExtract()</span><br><span class="line">                     .addIKM(initialKeyMaterial)</span><br><span class="line">                     .addSalt(salt).thenExpand(info, <span class="number">32</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Derive a 32-byte AES key</span></span><br><span class="line"><span class="type">SecretKey</span> <span class="variable">key</span> <span class="operator">=</span> hkdf.deriveKey(<span class="string">&quot;AES&quot;</span>, params);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Additional deriveKey calls can be made with the same KDF object</span></span><br></pre></td></tr></table></figure></div>

<p>バージョン24時点ではPreviewなので、コンパイル時と実行時に以下のオプションを指定する必要があります。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">javac --release 24 --enable-preview Foo.java</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">java --enable-preview Foo</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">または</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">java --enable-preview Foo.java</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">または</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">jshell --enable-preview</span></span><br></pre></td></tr></table></figure>

<h2 id="JEP-472-Prepare-to-Restrict-the-Use-of-JNI-（JNIの安全でない使用を制限）">JEP 472: Prepare to Restrict the Use of JNI （JNIの安全でない使用を制限）</h2><p>このJEPは、Java Native Interface の安全でない使用を将来的に制限するための準備です。ネイティブコードがJVMの整合性を損なう可能性のあるJNI関数を呼び出した際に、デフォルトで警告が表示されるようになりました。</p>
<h2 id="JEP-486-Permanently-Disable-the-Security-Manager-（セキュリティマネージャを恒久的に無効化）">JEP 486: Permanently Disable the Security Manager （セキュリティマネージャを恒久的に無効化）</h2><p>Java 17で非推奨となっていたセキュリティマネージャが、このバージョンでデフォルトで無効化されました。まだ完全な削除ではなく、コマンドラインオプションで有効化できます。</p>
<h2 id="JEP-496-497-Quantum-Resistant-（量子コンピュータによる攻撃に耐性）">JEP 496 &amp; 497: Quantum-Resistant （量子コンピュータによる攻撃に耐性）</h2><p>これら2つのJEPは、将来の量子コンピュータによる攻撃に耐えうる暗号技術を導入するものです。JEP 496では暗号通信のための鍵カプセル化メカニズム、JEP 497ではデジタル署名アルゴリズムが実装されました。</p>
<h2 id="JEP-498-Warn-upon-Use-of-Memory-Access-Methods-in-sun-misc-Unsafe-（sun-misc-Unsafeのメモリ操作メソッド使用時に警告）">JEP 498: Warn upon Use of Memory-Access Methods in sun.misc.Unsafe （sun.misc.Unsafeのメモリ操作メソッド使用時に警告）</h2><p><code>sun.misc.Unsafe</code> クラス内の特定のメモリ操作メソッドが使用された際に、警告が発せられるようになりました。これらのメソッドはJVMを不安定にするリスクがあるため、開発者には公式にサポートされている安全なAPIへの移行が推奨されています。</p>
<h2 id="おわりに">おわりに</h2><p>「ポスト量子」の世界がいよいよ現実味を増してきましたね。</p>
<p>次回は引続きJDK 24のご紹介が続きます。</p>
]]></content>
    <summary type="html">JDK 25のリリースを受けてバージョン24,25の変更点を紹介する連載企画の1本目の記事です。今回はJDK 24でのアップデート内容から以下についてピックアップしてご紹介します。</summary>
    <category term="Programming" scheme="https://future-architect.github.io/categories/Programming/"/>
    <category term="JEP" scheme="https://future-architect.github.io/tags/JEP/"/>
    <category term="Java" scheme="https://future-architect.github.io/tags/Java/"/>
    <category term="Java24" scheme="https://future-architect.github.io/tags/Java24/"/>
  </entry>
</feed>
