@ledsun blog

無味の味は佳境に入らざればすなわち知れず

引数13万個の関数呼び出し

Rubyバッチ処理File.deleteでたくさんのファイルを消していたらSystemStackError: stack level too deepという見慣れないエラーが起きました。 再帰などしていないのに、スタックを使い切るのはなぜでしょう?

File.delete(*to_delete)

こんな感じで呼び出していたので、配列展開になにかあるのかな?と思って試してみました。

irb(main):041:0> p(*(1..130795).to_a)
(irb):41:in `p': stack level too deep (SystemStackError)
  from (irb):41:in `<main>'
   ... 8 levels...
  from /Users/shigerunakajima/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
    from /Users/shigerunakajima/.rbenv/versions/3.0.1/bin/irb:23:in `load'
  from /Users/shigerunakajima/.rbenv/versions/3.0.1/bin/irb:23:in `<main>'

ところが、次のような関数呼び出しでない配列展開は

 [*(1..130795).to_a]

時間は掛かりますが、正常に動きます。

原因はよくわからないのですが、簡易な再現手順がわかったので、ruby-jpで聞いてみました。

結論としては、引数の数が多すぎるそうです。 id:ku-ma-me さんに教えてもらいました。

p(*(1..130795).to_a)の字面的に、引数が13万個あるように見えないのと、引数はスタックに積むものなので、言われてなるほどでした。

その他の面白情報

irbを使わないと限界数が232増えます。

~ ruby -e 'p(*(1..131027).to_a)'
-e:1:in `p': stack level too deep (SystemStackError)
  from -e:1:in `<main>'

id:Pockeさん情報です。

実際に13万個引数がある関数を定義して呼び出すと、SystemStackErrorが起きます。

irb(main):001:0> eval "def hoge(arg0,#{(1..130794).map { |i| "arg#{i} = nil" }.j
oin(",") }); puts arg0; end"
=> :hoge
irb(main):002:0> hoge 0
(eval):1:in `hoge': stack level too deep (SystemStackError)
  from (irb):2:in `<main>'
   ... 8 levels...
  from /Users/shigerunakajima/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
    from /Users/shigerunakajima/.rbenv/versions/3.0.1/bin/irb:23:in `load'
  from /Users/shigerunakajima/.rbenv/versions/3.0.1/bin/irb:23:in `<main>'

id:wakaba260yenさん情報です。 実行時間が長いので注意です。

JavaScriptでも同様に大量の引数をつけるとスタックオーバーフローが起きます。

> Math.min(...new Array(120615).fill(0))
Uncaught RangeError: Maximum call stack size exceeded
    at Math.min (<anonymous>)

id:tompngさん情報です。