メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#71151likely_accept

strings, bytes: add CutLast

ステータス変更: active likely_accept

要約

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

概要

strings および bytes パッケージに CutLast 関数を追加するproposalです。既存の strings.Cut(区切り文字の最初の出現位置で文字列を分割)の対になる関数として、区切り文字の最後の出現位置で文字列を分割する機能を標準ライブラリに追加することを目的としています。

ステータス変更

activelikely_accept
2026年4月8日のProposal Review Meeting(@aclements、@adonovan、@griesemer、@ianlancetaylor、@neild が参加)において、likely accept と判定されました。コミュニティでの広範な支持、実コードベース上での有用性の実証、および標準的なセマンティクスの必要性が主な決め手となりました。

技術的背景

現状の問題点

Go 1.18で strings.Cut が追加されて以来、「先頭から検索して分割」は標準化されましたが、「末尾から検索して分割」に相当する関数が標準ライブラリに存在しません。そのため、多くの開発者が同一パターンの関数を独自に実装しています。@rogpeppe は自身のコードベースで少なくとも5つの独自 cutLast 実装を発見しており、いずれも同一のコードパターンでした。

// 現状: 各プロジェクトが独自に実装
func cutLast(s, sep string) (before, after string, found bool) {
    if i := strings.LastIndex(s, sep); i >= 0 {
        return s[:i], s[i+len(sep):], true
    }
    return s, "", false
}

さらに、@rogpeppe は独自実装において「見つからない場合に "", s, false を返す」という逆のセマンティクスを採用していたところ、実際の使用箇所で誤った動作を引き起こしていたことを報告しています。標準化の必要性を裏付ける実例となりました。

提案された解決策

strings.CutLast および bytes.CutLast を標準ライブラリに追加します。

// CutLast slices s around the last instance of sep,
// returning the text before and after sep.
// The found result reports whether sep appears in s.
// If sep does not appear in s, CutLast returns s, "", false.
func CutLast(s, sep string) (before, after string, found bool) {
    if i := strings.LastIndex(s, sep); i >= 0 {
        return s[:i], s[i+len(sep):], true
    }
    return s, "", false
}

セマンティクスは strings.Cut と対称的で、見つからない場合は入力文字列全体を before に返し、after は空文字列となります。

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

コード例

// Before: strings.LastIndex を使った手動実装
i := strings.LastIndex(path, "/")
if i < 0 {
    dir, file = "", path
} else {
    dir, file = path[:i], path[i+1:]
}
// After: CutLast を使った簡潔な実装
dir, file, _ := strings.CutLast(path, "/")

実践的なユースケースとして以下が挙げられます。

  1. ファイルパスの処理: ディレクトリ部分とファイル名部分を分離する(path.Dir / path.Base が使えない一般的なパス文字列処理)
  2. URL・URIの解析: package-url 仕様など、末尾側から構造を解析するプロトコル処理(proposalの動機となった実例)
  3. ドット区切り識別子の分解: "a.b.c.Name" のような完全修飾名から最後の要素を取り出す処理

議論のハイライト

  • 命名: CutLast vs LastCut: LastIndex との一貫性から LastCut を支持する意見もあったが、文法的な自然さから最終的に CutLast に決定された
  • 未発見時のセマンティクス: return s, "", false(提案案)と return "", s, false(逆案)で議論があった。@rogpeppe の実証的な誤用事例から、return s, "", false が正しいと結論付けられた
  • CutSuffix との混同リスク: TrimSuffixTrimRight が混同されやすいように、CutLastCutSuffix も混同される懸念が指摘されたが、両者は戻り値の型が異なるため区別しやすいと判断された
  • 元々 Cut のproposalで言及済み: 2021年の strings.Cut 追加時(#46336)に LastCut として既に検討されており、「後で追加する価値があるかもしれない」と先送りにされていたものが今回正式に提案された
  • 標準化の価値: 実装が自明であっても、コードベース全体での一貫したセマンティクスを保証し、バグを防ぐために標準ライブラリへの追加が有意義と評価された

関連リンク