bytesパッケージに、セパレータの最後の出現位置でスライスを分割するCutLast関数を追加
bytes
概要
bytes パッケージに新しく CutLast 関数が追加された。sep の最後の出現位置を基準に s を分割し、LastIndex を使った既存の分割コードを置き換えて簡潔に書けるようになる。同名の関数が strings パッケージにも追加されており、両者は対称的な仕様を持つ。
導入経緯
提案者は package-url 仕様のパースなど、文字列の末尾側からセパレータを探して分割する処理を書くたびに、独自の CutLast 相当の関数を自前で実装せざるを得なかったことを課題として提起した(issue #71151)。議論の中で @rogpeppe が自身のコードベース内だけで少なくとも5つの独自 cutLast 実装を発見したことを報告し、標準化の必要性が広く支持された。
議論タイムライン
- 2026-03-25: Proposal Review Meeting の議題として active に追加。
- 2026-04-08: 議論を経て likely accept と判定。
- 2026-04-16: 前回からコンセンサスに変化がないことが確認され、正式に accepted。
議論のハイライト
- 関数名は
LastIndexとの一貫性を重視したLastCutと、CutXファミリーへの帰属を重視したCutLastの両案が検討され、最終的にCutLastが採用された。 - セパレータが見つからない場合の戻り値の設計が最も議論を呼んだ。@rogpeppe は自身が
"", s, falseを返す実装で繰り返し誤用していたことを報告し、strings.Cutと対称的なs, "", falseを返す仕様の妥当性を実証的に裏付けた。 CutSuffixとの混同が懸念されたが、戻り値の型が異なるため実際の誤用は少ないと判断された。- 実装 CL(CL 764601)が誤って自動マージ可能な状態になっており、コメント期間中に取り込まれるという経緯があったが、コンセンサスに変化がなかったため取り消されず、そのまま accepted となった。
使用例
Before
package main
import (
"bytes"
"fmt"
)
func cutLast(s, sep []byte) (before, after []byte, found bool) {
if i := bytes.LastIndex(s, sep); i >= 0 {
return s[:i], s[i+len(sep):], true
}
return s, nil, false
}
func main() {
host, port, ok := cutLast([]byte("example.com:8080:443"), []byte(":"))
fmt.Println(string(host), string(port), ok)
}
After
package main
import (
"bytes"
"fmt"
)
func main() {
host, port, ok := bytes.CutLast([]byte("example.com:8080:443"), []byte(":"))
fmt.Println(string(host), string(port), ok)
}
移行時の注意
独自実装の cutLast を bytes.CutLast に置き換える際は、セパレータが見つからない場合の戻り値の仕様に注意が必要である。CutLast は見つからない場合 s, nil, false を返す(before に元のスライス全体が入り、after は nil)。これは bytes.Cut と対称的な仕様だが、"", s, false を返す独自実装を使っていた場合は呼び出し側のロジックを見直す必要がある。
実装解説
bytes.CutLast の実装は src/bytes/bytes.go;l=1430 にあり、LastIndex でセパレータの最後の出現位置を求め、見つかった場合はその前後でスライスするだけの単純な実装である。返されるスライスは元のスライス s の一部を参照する(コピーしない)。
対となる strings.CutLast は src/strings/strings.go;l=1295 にあり、同様に LastIndex を用いたロジックで、見つからない場合は s, "", false を返す。