Techioz Blog

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

これは、利回り値が移動され、送信者がアクセスできなくなるためです。