Ruby のメソッドのブールフラグは何をするのでしょうか?
概要
Ruby の Ractor.select メソッドの移動フラグの動作を理解しようとしています。 メソッドのシグネチャ: https://ruby-doc.org/3.2.1/Ractor.html#method-c-current
ドキュメント内の関数の移動フラグの説明では、フラグが次のように説明されています。
move boolean flag defines whether yielded value should be copied (default) or moved.
このステートメントは、Rust のような所有権モデルが導入されている言語では意味を成します。でも、それが Ruby にとって何を意味するのか理解しようとしている
このフラグの効果を実験するためにコードを実行しました。
should_move = true
puts "MOVE: #{should_move}"
r1 = Ractor.new do
data = [{name: "tom"}, {name: "dick"}, {name: "harry"}]
puts "[INSIDE RACTOR] data value is #{data} and data object id is :#{data[0].object_id}"
Ractor.yield(data)
sleep 2
puts "[INSIDE RACTOR] data value is #{data} and data object id is :#{data[0].object_id}"
end
ractor, value = Ractor.select(r1, move: should_move)
puts "[OUTSIDE RACTOR] value is: #{value} and value[0] object_id is: #{value[0].object_id}"
p ractor
sleep 3
上記のコードでは、 should_move = true と should_move = false の両方で出力は同じでした。
MOVE: true
main.rb:4: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues.
[INSIDE RACTOR (before move)] data value is [{:name=>"tom"}, {:name=>"dick"}, {:name=>"harry"}] and data object id is :60
[OUTSIDE RACTOR (after move)] value is: [{:name=>"tom"}, {:name=>"dick"}, {:name=>"harry"}] and value[0] object_id is: 80
#<Ractor:#2 main.rb:4 blocking>
[INSIDE RACTOR (after move)] data value is [{:name=>"tom"}, {:name=>"dick"}, {:name=>"harry"}] and data object id is :60
そして同じことを実行してみましたが、今回は次のようにラクター内のデータ変数を整数値に設定しました。
should_move = true
puts "MOVE: #{should_move}"
r1 = Ractor.new do
data = 12
puts "[INSIDE RACTOR] data value is #{data} and data object id is :#{data.object_id}"
Ractor.yield(data)
sleep 2
puts "[INSIDE RACTOR] data value is #{data} and data object id is :#{data.object_id}"
end
ractor, value = Ractor.select(r1, move: should_move)
puts "[OUTSIDE RACTOR] value is: #{value} and value object_id is: #{value.object_id}"
p ractor
sleep 3
今回、この場合の出力は次のようになりました。
MOVE: false
main.rb:4: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues.
[INSIDE RACTOR (before move)] data value is 12 and data object id is :25
[OUTSIDE RACTOR (after move)] value is: 12 and value[0] object_id is: 25
#<Ractor:#2 main.rb:4 blocking>
[INSIDE RACTOR (after move)] data value is 12 and data object id is :25
この場合も、移動フラグの状態は出力に影響しませんでした。
解決策
Ractor::select のコンテキストで移動が何を行うか、またそれがどのような影響を与えるかを見逃していると思います
move を実行すると、送信されたオブジェクトに送信者がアクセスできなくなります。これは、例を少し変更することで確認できます。
r1 = Ractor.new do
data = [{name: "tom"}, {name: "dick"}, {name: "harry"}]
puts "[INSIDE RACTOR] data value is #{data} and data object id is :#{data[0].object_id}"
Ractor.yield(data, move: true)
begin
puts "[INSIDE RACTOR] data value is #{data} and data object id is :#{data[0].object_id}"
rescue Ractor::MovedError => e
puts
puts "data is no longer accessible because it was moved"
end
end
Ractor.select(r1)
出力:
[INSIDE RACTOR] data value is [{:name=>"tom"}, {:name=>"dick"}, {:name=>"harry"}] and data object id is :740
data is no longer accessible because it was moved
これは、メッセージ データが move:true を指定して yield 経由で送信されるためです。
Ractor::select では、移動は yield_value に対応します。
data = [{name: "tom"}, {name: "dick"}, {name: "harry"}]
r1 = Ractor.new(Ractor.current) do |main|
puts "Received from main: #{main.take}"
end
puts "Ractor.select with yield_value: #{data} and move: true"
Ractor.select(r1, yield_value: data, move: true)
begin
puts "Can access data: #{data}"
rescue Ractor::MovedError => e
puts
puts "data is no longer accessible because it was moved"
end
出力:
Ractor.select with yield_value: [{:name=>"tom"}, {:name=>"dick"}, {:name=>"harry"}] and move: true
Received from main: [{:name=>"tom"}, {:name=>"dick"}, {:name=>"harry"}]
data is no longer accessible because it was moved
これは、利回り値が移動され、送信者がアクセスできなくなるためです。