😊
[Bug #21525] ブロック引数があるメソッドの評価順序が一貫していないバグ報告
[Bug #21525] Inconsistent execution order for methods with constant blocks
- 次のようにメソッド引数で
*を使用している場合に『変更前の配列のコピー』がメソッドの引数に渡される - なので次のようにブロック引数に副作用がある場合でも『変更前の配列』がメソッドに渡される
ARRAY = [1, 2, 3]
# このメソッドで ARRAY の末尾が削除される
def bar
ARRAY.pop
-> {}
end
def example(*args, &)
args
end
# bar で ARRAY の値が書き換わるがメソッドの引数は書き換わる前の値が渡される
pp example(*ARRAY, &bar)
# => [1, 2, 3]
# ARRAY 自体の値は変わっている
pp ARRAY
# => [1, 2]
- しかし、次のように
#to_procで値を書き換えた場合に『書き換わった後の値』を渡す可能性がある
ARRAY = [1, 2, 3]
module Foo
module Bar
# &Foo::Bar のタイミングで #to_proc が呼び出される
def self.to_proc
ARRAY.pop
-> {}
end
end
end
def example(*args, &)
args
end
pp example(*ARRAY, &Foo::Bar)
# => [1, 2]
- また次のように
const_missingで書き換えた場合にも同様の結果になる
ARRAY = [1, 2, 3]
module Foo
def self.const_missing(name)
ARRAY.pop
-> {}
end
end
def example(*args, &)
args
end
pp example(*ARRAY, &Foo::Bar)
# => [1, 2]
- これらはいずれも
[1, 2]ではなくて[1, 2, 3]を期待する、というバグ報告 - これは評価順序の問題になるんですがコメントを見るとアロケーションを回避するための挙動とのトレードオフになるみたいですね
- なので意図的にこういう挙動になっているみたい
- https://bugs.ruby-lang.org/issues/21525#note-1
- これは他にもエッジケースがあるみたいで以下のようなコードの場合に
method(*args, **kwargs)
method(*args, &block)
method(**kwargs, &block)
-
kwargs.to_hashやblock.to_procがargsやkwargsを変更すると同様に評価順序の問題が発生するみたい - これらを対応する場合に VM 命令が必要になりエッジケースのためだけに対応するのはコストがかかるのでそれなりの理由が必要になるとのこと
- これ自体は起票者も納得しているのでチケット自体はクローズされている
Discussion