Timee Product Team Blog

タイミー開発者ブログ

【RubyKaigi 2024 参加レポート】Namespaceを実際に触ってみた

こんにちは、タイミーの @masarakki です。

先日、5月15日から3日間開催された「RubyKaigi2024」に参加しました。
本記事で取り上げるのは、そのRubyKaigi2024の最後のセッションであるmatzのキーノートで、「これが入ったらRuby 4.0」とまで言われた @tagomoris 氏のNamespace機能。 セッション終了後、目の前に本人が座っていたので「責任重大だねwww」と煽りに行こうとしたところ、感極まって帽子を目深に被りなおしている瞬間だったのでそっとしておきました。

というわけで、セッションの内容 は他にいくらでも記事があると思うので、実際に手を動かしてみようと思います。

参考: https://gist.github.com/tagomoris/4392f1091f658294bd4d473d8ff631cb

作業ブランチが Namespace on readにあるのでビルドしてみましょう。

$ git clone https://github.com/tagomoris/ruby
$ cd ruby
$ git checkout namespace-on-read
$ ./autogen.sh
$ mkdir build
$ cd build
$ ../configure --prefix=$HOME/ns-ruby
$ make
$ make install

$ ~/ns-ruby/bin/ruby -v
ruby 3.4.0dev (2024-03-28T13:58:33Z namespace-on-read f0649a2577) [x86_64-linux]

どうやらうまくビルドできたようです (rubyのビルド人生で初めてやった)。

かんたんな検証コードを動かしてみましょう。

# foo.rb ------

require './bar'

class Foo
 def self.var=(val)
    @@var = val
  end

  def self.var
    @@var
  end
end

# -------------

# bar.rb ------

class Bar
end

# -------------

# nstest.rb ---

def dump(obj)
  puts "#{obj}: #{obj.object_id}"
end

require './foo'

ns = Namespace.new
ns.require './foo'

dump Foo
dump Bar

dump ns::Foo
dump ns::Bar

Foo.var = 'abc'
ns::Foo.var = 'xyz'

puts "#{Foo.var}, #{ns::Foo.var}"

# -------------

実行してみましょう。

~/ns-ruby/bin/ruby nstest.rb
Foo: 100
Bar: 120
#<Namespace:0x00007ff908cf1cd8>::Foo: 140
#<Namespace:0x00007ff908cf1cd8>::Bar: 160
abc, xyz

Foons::Foo が全く独立していることがわかります。

普段遣いのrubyでは動かないことを確認しましょう。

$ ruby -v
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-linux]
$ ruby nstest.rb
nstest.rb:8:in `<main>': uninitialized constant Namespace (NameError)

ns = Namespace.new
     ^^^^^^^^^

確かセッションでは例として Oj.default_options行儀の悪い gem によって書き換えられてしまう事例が挙げられていたので試してみましょう。

# foo.rb ------

require 'oj'
Oj.default_options = { symbol_keys: true }

class Foo
  def self.oj
    Oj.load('{"key":"symbol_or_string"}'
  end
end

# -------------

# nstest.rb ---

require './foo'

ns = Namespace.new
ns.require './foo'

Oj.default_options = { symbol_keys: false }

p Foo.oj
p ns::Foo.oj

実行してみましょう。

$ ~/ns-ruby/bin/gem i oj
$ ~/ns-ruby/bin/ruby nstest.rb
$HOME/ns-ruby/lib/ruby/3.4.0+0/date.rb:51: [BUG] Segmentation fault at 0x0000000000000168
ruby 3.4.0dev (2024-03-28T13:58:33Z namespace-on-read f0649a2577) [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0013 p:0039 s:0052 e:000051 CLASS  $HOME/ns-ruby/lib/ruby/3.4.0+0/date.rb:51 
_人人人人人人人_
> 突然のSEGV <
 ̄Y^Y^Y^Y^Y^Y^Y^ ̄

ちなみに date.rb:51def coerce(other) です。 なぜ・・・
そういえばセッションで @tagomoris も祈りながら実行していたな・・・というのを思い出し、何度か実行してみたところ、確率10%くらいで成功しました。

$ ~/ns-ruby/bin/ruby nstest.rb
{"key"=>"symbol_or_string"}
{:key=>"symbol_or_string"}

他のライブラリでも試してみましょう。

json

標準ライブラリで oj と同じようにC拡張を持つライブラリです。
特に何も問題なく読み込めました。

csv '<top (required)>': uninitialized constant Array (NameError)

ネームスペースの中でArrayが見つからないみたいです。
もっと単純なコードで試してみましょう。

# foo.rb -----
puts "#{Array}: #{Array.object_id}"

module Mod
  def foo
    p :foo
  end
end
Array.include(Mod)

# ------------

# nstest.rb --

puts "#{Array}: #{Array.object_id}"

ns = Namespace.new
ns.require './foo'

[].foo

# ------------
$ ~/ns-ruby/bin/ruby nstest.rb
Array: 80
Array: 80
:foo

なにかおかしいですね。
Array は見つかるものの、予想外にトップレベルの Array まで汚されてしまっています。

さらに foo.rbrequire 'csv' を追加すると

$ ~/ns-ruby/bin/ruby nstest.rb
$HOME/ns-ruby/lib/ruby/gems/3.4.0+0/specifications/csv-3.2.8.gemspec:4: [BUG] vm_cref_dup: unreachable
_人人人人人人人_
> 突然のSEGV <
 ̄Y^Y^Y^Y^Y^Y^Y^ ̄

トップレベルで require ‘csv’ した後に ns.require './foo' するとuninitialized constant Array (NameError)のエラーに戻ります

jsoncsv

$ ~/ns-ruby/bin/ruby nstest.rb                                                                                                                      
$HOME/ns-ruby/lib/ruby/3.4.0+0/forwardable.rb:230: [BUG] Segmentation fault at 0x00000000000001da

-- Control frame information -----------------------------------------------
c:0017 p:---- s:0088 e:000087 CFUNC  :proc
c:0016 p:0004 s:0084 E:001920 TOP    $HOME/ns-ruby/lib/ruby/3.4.0+0/forwardable.rb:230 [FINISH] 
_人人人人人人人_
> 突然のSEGV <
 ̄Y^Y^Y^Y^Y^Y^Y^ ̄

問題のない jsonと問題のある csv を両方requireするとなぜか別の問題が発生しました。
面白いですね。
(自分のrubyのビルド方法が間違ってる疑惑・・・?)
こういう開発途中の機能を触ってみるのは初めてなので、Rubyがこんなに簡単にぶっ壊れるんだ・・・って新鮮な気持ちです。

いろいろなパターンでSEGVが出たので次回はコードを読んでみたいと思います。

最後に

コロナ禍で人生が一変し、結婚 + 出産 * 2 が続き、2019年以来 実に5年ぶりのRubyKaigi参加になりました。
RubyKaigi、やっぱりドチャクソ楽しいですね・・・特にrubykaraoke・・・ッ!!
1歳と0歳を連れて参加できたのも、ひとえに託児所やイベントに子供を受け入れてくださった各社様のおかげです。この場を借りてお礼申し上げます。

なお、RubyKaigiには「Kaigi Pass」という制度を利用し参加しました。
Kaigi Passとは、世界中で開催されている全ての技術カンファレンスに無制限で参加できるタイミーの制度です。
制度について気になる方はぜひ以下の記事もご覧ください。

productpr.timee.co.jp