RubyKaigi Takeout 2020 の延長線で盛り上がってた Symbol#to_s について

[ブログ記事](https://secret-garden.hatenablog.com/entry/2020/09/08/211023)の転載です。

[RubyKaigi Takeout 2020](https://rubykaigi.org/2020-takeout) お疲れ様でした。
今回はオンラインということではじめて参加したんですが興味深い内容の話があって楽しかったです。
特に sawanobori からのサンダル紹介、焚き火で飯を炊いて魚を焼くコミッタの日常配信はニューノーマルを感じました。
いやー Ruby コミッタともなると過酷な環境で開発する技術が必要なんですねー勉強になります！
あ、あと Refinements はとてもすばらしく最高な機能なので皆さんどんどん使っていってニューノーマルになっていきましょう。

さてさて、まだまだ RubyKaigi 熱は冷めやらぬのですが今回は 1日目の [Ruby Committers vs the World](https://rubykaigi.org/2020-takeout/speakers#rubylangorg) で盛り上がってた `Symbol#to_s` の話について簡単にまとめてみました。


## `Symbol#to_s` で frozen String を返す提案

ちょうど去年の今頃 `#to_s` で返す文字列を `frozen` にしようという提案がされました。

* チケット：[Feature #16150: Add a way to request a frozen string from to_s - Ruby master - Ruby Issue Tracking System](https://bugs.ruby-lang.org/issues/16150)

このチケットで本題の『`Symbol#to_s` の戻り値 frozen にしよう』という話が出てきました。
また、このチケットでは `Symbol#to_s` だけではなくて以下のメソッドも frozen String を返す変更が含まれています。

* `Module#name`
* `TrueClass/FalseClass#to_s`
* `NilClass#to_s`

関連する PR や経緯などは以下のリンクを参考にしてください。

* まつもとさんのコメント： [Feature #16150: Add a way to request a frozen string from to_s - Ruby master - Ruby Issue Tracking System](https://bugs.ruby-lang.org/issues/16150#note-21)
* [Make Symbol#to_s return a frozen String, the same String for a given Symbol by eregon · Pull Request #2437 · ruby/ruby · GitHub](https://github.com/ruby/ruby/pull/2437)
* [[EXPERIMENTAL] Make Module#name return a frozen String by byroot · Pull Request #2487 · ruby/ruby · GitHub](https://github.com/ruby/ruby/pull/2487)
* [[EXPERIMENTAL] Make NilClass / TrueClass / FalseClass #to_s return a frozen String by byroot · Pull Request #2492 · ruby/ruby · GitHub](https://github.com/ruby/ruby/pull/2492)


この変更はまつもとさんから承認され、マージ後に Ruby 2.7 preview2 で『実験的な機能』としてプレリリースされました。


## Ruby 2.7 で変わるはず『だった』 `Symbol#to_s`

`Symbol#to_s` が frozen String を返すようになってパフォーマンスが上がってよかったよかった。
で、終わればよかったんですが `Symbol#to_s` の変更をマージしたらいずれかの gem で問題があることがわかりました。
例えば `pry` では次のように `Symbol#to_s` の戻り値を破壊的に変更するコードが書かれていて影響が出ていました。

```ruby
def method_missing(method, *args, &block)
  meth = method.to_s
  if meth.end_with?('?')
    # to_s の戻り値を破壊的に変更しているので frozen String だとエラーになる…
    meth.chop!
    present?(meth) || present?(meth.tr('_', '-'))
  else
    super
  end
end
```

このように `Symbol#to_s` の戻り値を破壊的に変更しているコードはいくつか報告され、また Ruby 2.7 ではキーワード引数周りの対応もあったので混乱を避けるために Ruby 2.7 では `Symbol#to_s` の対応は見送られることとなりました。
また、Revert されたのは `Symbol#to_s` の変更だけで以下のメソッドは frozen String を返すように変更され Ruby 2.7 の機能としてリリースされました。

* `Module#name`
* `TrueClass/FalseClass#to_s`
* `NilClass#to_s`


## 1年の時を経て…

先月開催された [RubyKaigi Takeout 2020](https://rubykaigi.org/2020-takeout) の [Ruby Committers vs the World](https://rubykaigi.org/2020-takeout/speakers#rubylangorg) の延長戦でちょうど `Symbol#to_s` の議論がされていました。
そこでも `Symbol#to_s` をどうするのかいろいろと議論されていたんですが 1つの結論として `Symbol#to_s` を非互換にするのではなくて `Symbol#name` という別名のメソッドを追加する流れとなりました。
そしてその当日に[ `Symbol#name` が開発版へとマージ](https://github.com/ruby/ruby/pull/3514)されました。勢いある。


## [#16150](https://bugs.ruby-lang.org/issues/16150) の時系列

* 2019/09/07：[チケットが立つ](https://bugs.ruby-lang.org/issues/16150)
* 2019/09/19：[まつもとさんが承認](https://bugs.ruby-lang.org/issues/16150#note-21)
* 2019/09/26：[ `Symbol#to_s` と `Module#name` の変更マージされる](https://bugs.ruby-lang.org/issues/16150#note-22)
* 同日：[この変更により影響が受けている gem の報告がされる](https://bugs.ruby-lang.org/issues/16150#note-29)
* 2019/09/27：[ `NilClass#to_s` と `TrueClass#to_s` `FalseClass#to_s` の変更マージされる](https://bugs.ruby-lang.org/issues/16150#note-37)
〜〜〜
* 2019/10/22：[Ruby 2.7.0-preview2 リリースされる](https://www.ruby-lang.org/ja/news/2019/10/22/ruby-2-7-0-preview2-released/)
〜〜〜
* 2019/11/01：[Heroku で問題があると報告が上がる](https://bugs.ruby-lang.org/issues/16150#note-45)
    * [Heroku 側の issues](https://github.com/heroku/heroku-buildpack-ruby/issues/833)
* 同日：[まつもとさんが Revert すると判断](https://bugs.ruby-lang.org/issues/16150#note-46)
* 2019/11/05：[Revert された](https://bugs.ruby-lang.org/issues/16150#note-52)
    * コミット：https://github.com/ruby/ruby/commit/bea322a3
〜〜〜
* 2020/09/04：[frozen String を 返す `Symbol#name` が入った](https://bugs.ruby-lang.org/issues/16150#note-68)
    * PR：https://github.com/ruby/ruby/pull/3514
* 2020/09/05：[ActiveSupport の `HashWithIndifferentAccess` で `Symbol#name` を呼び出す PR が投げられる](https://github.com/rails/rails/pull/40182)
〜〜〜
* 2020/12/25：Ruby 3.0 がリリースされる（予定）


## まとめ

と、言う感じで `Symbol#to_s` 問題について簡単にまとめてみました。
結果だけ見れば影響範囲が大きい非互換だったんですが、実際にリリースしてみるまでわからない問題が存在するのはある程度は仕方ないのでむずかしいですねえ。
むしろ今回のケースではプレリリース版で問題があることがわかったのでプレリリースとしては正しい動きをしていた例とも言えますね。
正式版で問題になる前にわかってよかったよかった。
みなさんも Ruby 3.0 preview 版が出たらどんどん使ってみて問題がないか試してみましょう。


## 関連

* [Ruby: Symbol#to_sはRuby 2.7 previewでfrozen Stringを返したが今は違う（翻訳）｜TechRacho（テックラッチョ）〜エンジニアの「？」を「！」に〜｜BPS株式会社](https://techracho.bpsinc.jp/hachi8833/2020_07_15/89485)