メインコンテンツへスキップ
#3939likely_accept

spec: remove string(int)

ステータス変更: active likely_accept

要約

AIによる要約であり、誤りを含む場合があります。

概要

string(int)変換(整数値を1文字のUnicode文字列に変換する組み込み変換)を、byteまたはrune型の整数に限定し、それ以外の整数型からの変換を言語仕様上禁止する提案です。長らくgo vetの警告としてのみ存在した制限を、Go 1.28以降の言語仕様レベルに格上げする内容です。

ステータス変更

active → likely_accept
2026年9月9日の提案レビュー会議で、コアチームはこの変更に賛成の姿勢を示し、「基本的に現行のgo vetチェック(stringintconv)と同じ挙動に合わせる」という方針を確認しました。細かい論点として「string(0)のような型なし定数リテラルの扱いをどうするか」が議論され、通常の型のデフォルト化ルールに従わせた上でintを拒否する形にすることで合意しました。具体的な仕様文言はGriesemer氏が執筆することになり、実際にCL 830044(spec変更)とCL 832604(go/types, types2の実装)が提出されたことを受け、9月16日にAclements氏が「likely accept」と判定しました。

技術的背景

現状の問題点

現行のGo仕様では任意の整数型をstringに変換でき、これはUTF-8エンコードされたコードポイントに変換されます。しかし以下のような問題があります。

i := 3
s := string(i) // 意図: "3" を期待しがちだが、実際は制御文字(ETX, U+0003)になる
  • サロゲート領域(0xD800〜0xDFFF)の値は正当なUTF-8にできないため、代わりに置換文字�になるなど直感的でない挙動がある。
  • シフト演算の結果を渡すと定数の扱いが複雑化する(関連issue #26096)。
  • 「int→string変換で数値の文字列表現が得られる」と誤解するバグが繰り返し報告されている(Python等のstr(42)との混同)。
  • 2016年頃から議論が続き、2019年にGo vetのstringintconvチェック(#32479)が追加されたことで、実運用上の危険性は大幅に緩和されていました。

提案された解決策

Go 1.28以降の言語バージョンにおいて、string(x)のxが整数型の場合、byte(uint8)またはrune(int32)型(およびそれらのエイリアス、または型なしrune定数)でなければコンパイルエラーとする、という仕様変更です。仕様文言の差分(CL 830044より抜粋)は以下の通りです。

 	<li>
-	x is an integer or a slice of bytes or runes
-	and T is a string type.
+	x is an integer of type byte or rune
+	or an untyped rune constant
+	or a slice of bytes or runes and T is a string type.
 	</li>

重要なのは、これが言語バージョン依存の変更である点です。go.modで古い言語バージョンを指定しているモジュールは従来通り動作し、新しい言語バージョン(1.28以降)を指定した場合のみ制限が適用されます。これにより既存コードへの後方互換性への影響を最小化しています。

これによって何ができるようになるか

これは「新機能の追加」ではなく「危険な変換の禁止」による安全性向上が主目的です。開発者にとってのメリットは以下の通りです。

  • 意図しないstring(int)のバグ(数値の文字列表現化を期待して実は制御文字になる、など)をコンパイル時に検出できる。従来はgo vetを明示的に実行しないと検出できなかった。
  • byte/runeからの変換(string(os.PathSeparator)のような一般的で正当な用法)は引き続き許可されるため、実用上のワークアラウンドはほぼ不要。
  • IDEやリンターに頼らず、コンパイラレベルでより分かりやすいエラーメッセージが得られる。

コード例

// Before (Go 1.27以前、またはgo.modでlanguage version < go1.28を指定):
i := 3
s := string(i) // コンパイルは通るが、"3"ではなく制御文字(ETX)になる意図しない結果
                // go vetを実行すれば stringintconv 警告が出るが、通常のビルドでは検出されない
// After (go.modで go 1.28 以上を指定した場合):
i := 3
s := string(i) // コンパイルエラー: cannot convert i (variable of type int) to type string
// 正しい書き方(数値を文字列表現にしたい場合)
s := strconv.Itoa(i) // "3"
// byte/runeへの変換は引き続き有効
var b byte = 65
s2 := string(b) // "A" — これは今後も許可される
var r rune = '文'
s3 := string(r) // "文" — これも許可される

議論のハイライト

  • 10年以上に渡る慎重な議論: 2013年の提案当初は「Go 1互換性を壊す」懸念から長期保留されていましたが、2019年にgo vetのstringintconvチェック(#32479)が導入されたことで実運用データが蓄積され、議論が再燃しました。
  • byte/runeの特例扱いの根拠: Ian Lancetaylor氏や多くのコメンターが、string(os.PathSeparator)やstring('x')のような一般的で正当な用法を壊さないため、byteとrune型だけは除外すべきと主張し、これが最終的な提案の核となりました。
  • 2026年7月の一度目の判定は「likely decline」だった点が特筆されます。Griesemer氏は当初「vetチェックが既に十分機能しており、仕様変更のメリットが小さい」として却下寄りの判断を示しましたが、Aclements氏の「言語バージョンに紐付ければ既存コードを一切壊さずに、コンパイルエラーとしてより強い保証が得られる」という指摘により方針が転換し、「likely accept」へ再評価されました。
  • 型なし定数の扱いの明確化: string(0)のような型なし整数定数の扱いについて、通常の型のデフォルト化規則に従わせつつintを拒否するという整合的なアプローチが採用されました。
  • エイリアス関係の確認: 提案後の議論でrune/int32、byte/uint8がエイリアス(同一の型)であることを踏まえ、string(int32(1))やstring(uint8(1))もstring(rune(1))やstring(byte(1))と同様に許可されることがGriesemer氏により明確化されました。

関連リンク