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

spec: remove string(int)

ステータス変更: likely_accept accepted

要約

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

概要

整数値をstring型へ変換するstring(int)という古い言語仕様を制限し、byte(uint8)またはrune(int32)型の整数、および無型のrune定数のみを許可する提案です。Go 1.28以降の言語バージョンから、それ以外の整数型からの変換はコンパイルエラーになります。

ステータス変更

likely_accept → accepted
2026年9月9日のレビュー会議で、コアチーム全員がこの変更に賛成し、griesemer氏が具体的な仕様文言を執筆することで合意しました。9月16日にlikely acceptとして最終コメント募集期間に入り、9月24日、最終コメント期間中に議論の大筋が変わらなかった(no change in consensus)ため、aclements氏により正式にacceptedと判定されました。

技術的背景

現状の問題点

Go言語には昔から、任意の整数型をstringに変換すると、その整数値をUnicodeコードポイントとみなしてUTF-8エンコードされた1文字の文字列を生成するという変換規則があります。

i := 3
s := string(i) // "\x03" のような1バイト文字列になる(数値の文字列表現"3"にはならない)

これは初心者が「数値を文字列表現に変換する」と誤解しやすい罠であり、サロゲート値(例: string(0xD800))のように本来UTF-8として不正な値を渡すと、暗黙に置換文字�が生成されてしまうなど、動作が直感的でないという問題が長年指摘されてきました(issue自体は2013年にrsc氏(当時robpike氏の提案として)作成された古い提案です)。この問題はgo vetのstringintconvチェックとして2019年から静的解析レベルでは警告されてきましたが、vetはコンパイラに統合されておらず、必ずしも全ての開発フローで実行されるとは限りませんでした。

提案された解決策

言語仕様を変更し、string(x)のxが整数型の場合、byte(uint8)・rune(int32)・無型のrune定数のいずれかでなければコンパイルエラーとします。この制限は言語バージョンがgo1.28以上のモジュールにのみ適用され、既存のGoモジュール(go1.27以下を指定するもの)のコードは今まで通り動作し続けます。

 A non-constant value x can be converted to type T in any of these cases:
 ...
-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.

byte/runeという表記は、それぞれのエイリアスであるuint8/int32にも同様に適用されます(Go仕様上、エイリアスは同一の型を指すため)。

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

この変更自体は新しい機能を追加するものではなく、既存のgo vetのstringintconvチェックをコンパイラレベル(言語仕様)に格上げするものです。これにより、vetを実行しない環境でも危険な変換がコンパイルエラーとして検出されるようになり、バグの早期発見につながります。一方で、string(os.PathSeparator)やstring(rune(65))のような、意図的に1文字の文字列を作るためのbyte/rune変換は引き続き利用可能です。

コード例

// Before (Go 1.27以前、またはgo1.27以下を指定するモジュール):
i := 3
s := string(i) // コンパイルは通るが、"3"ではなく制御文字のUTF-8表現になる(バグの温床)
const sep = string(os.PathSeparator) // これは意図通りに動作する(rune相当)
// After (go1.28以上を指定するモジュール):
i := 3
s := string(i) // コンパイルエラー: string(x)にはbyteまたはrune型が必要
// 意図を明確にした正しい書き方
s := strconv.Itoa(i)          // 数値の文字列表現が欲しい場合
s := string(rune(i))          // 1文字のUTF-8文字列が欲しい場合(意図を明示)
const sep = string(os.PathSeparator) // byte/rune型なので引き続き有効

議論のハイライト

  • 長期塩漬けからの方針転換: 2020年にianlancetaylor氏が「vetチェックの実績とGoモジュールの言語バージョン管理が普及するまで保留」とし、2026年7月にはgriesemer氏が「実利が小さく破壊的変更のリスクもある」として一度はlikely declineと判定しましたが、aclements氏が「言語バージョンに紐付ければ既存コードは壊れない」と指摘したことで方針が転換し、最終的に受理に至りました。
  • rune/byteの例外化: ianlancetaylor氏やrsc氏の初期の議論で、string(os.PathSeparator)のようなrune定数を使った実用例が多数存在することが指摘され、完全撤廃ではなくbyte/runeのみ許可する現実的な折衷案が採用されました。
  • エイリアスの扱いに関する誤解: mateusz834氏が「string(int32(1))はエラーになるのにstring(rune(1))は動く、これらはエイリアスのはずでは」と指摘しましたが、griesemer氏が「runeとint32、byteとuint8は同一型として同じ扱いになる」と説明し、誤解を解消しました。
  • 影響範囲の実測調査: TheCoreMan氏がSourcegraphで実コードを検索し、string(0)や制御文字、ASCII文字変換など実際の使用例を確認した上で「言語バージョンに紐付くなら安全に撤廃できる」と賛成しました。
  • 後方互換性への懸念: jub0bs氏が「Go 1.0以降で初の破壊的変更では」と懸念を示しましたが、griesemer氏は「forループの変数セマンティクス変更が先例としてある」こと、また「go1.28以上を明示したモジュールにのみ影響し、vetが長年警告してきた内容そのもの」であることを説明し、懸念に対応しました。

関連リンク