Go言語に出したプロポーザルが通った:{bytes,strings}.ContainsFuncの追加

今年の夏にGo言語に以下のようなプロポーザルを出していたのですが、それが先ほど承認されました。標準パッケージの関数追加になります。 proposal: bytes, strings: add ContainsFunc · Issue #54386 · golang/go · GitHub

Go言語のstringsパッケージbytesパッケージには、文字列から文字や部分文字列を探す関数がいくつかあります。 探す文字の位置を返す関数、最後から探す関数、そういう文字が含まれるかどうかを返す関数を表にまとめると、次のようになります。

Find what? Index* LastIndex* Contains*
substr string Index(s, substr string) int LastIndex(s, substr string) int Contains(s, substr string) bool
chars string IndexAny(s, chars string) int LastIndexAny(s, chars string) int ContainsAny(s, chars string) bool
c byte IndexByte(s string, c byte) int LastIndexByte(s string, c byte) int --
r rune IndexRune(s string, r rune) int -- ContainsRune(s string, r rune) bool
f func(rune) bool IndexFunc(s string, f func(rune) bool) int LastIndexFunc(s string, f func(rune) bool) int Proposal #54386

私がプロポーザルで出したのは、関数で指定した条件に当てはまる文字が含まれるかどうかというContainsFunc関数です。 文字列の中から関数で条件を指定して当てはまる文字を探すためには、IndexFuncの返り値から判定しなくてはいけませんでした。

hasControlRune := strings.IndexFunc(target, unicode.IsControl) >= 0

これで良いという話ではありますが、いつもいつも数字と比較するのは面倒ですし、読みやすくもありません。 それに >= 0!= -1 かといった書き方の好みも分かれてしまい、どちらが良いかというナンセンスな議論を生んでしまいます。 strings.ContainsFuncがあれば、コードの意図がより分かりやすくなるのではないでしょうか。

hasControlRune := strings.ContainsFunc(target, unicode.IsControl)

最初はgojqを書いている時に、上のような判定コードでstrings.ContainsFuncが欲しくなったというのがきっかけでした。 この関数があるとどれくらい嬉しいかということを主張するために、semgrepでGo言語のリポジトリをスキャンしてみたのですが、strings.IndexFuncbytes.IndexFuncを使っている9箇所のうち、6箇所がContainsFuncで置き換えられることが分かりました。 過半数が文字が含まれているかの判定で使われていて、位置自体を欲しいケースは半分以下ということですね。 これはContainsFunc追加をサポートする良い材料だと思い、プロポーザルに書いてみました(本来はもっと大きなコードベースで調査した方がいいのでしょう)。

まずslices.ContainsFuncという別のプロポーザルが承認され、これと関連している私のプロポーザルも先ほど承認されました。 Go言語にプロポーザルを出したのは初めてでしたが、特に白熱した議論が繰り広げられることもなく、すんなり承認されましたね。 自分の提案がきっかけで実装される機能が世に出るのは楽しみです。 \広告です/