From: "ko1 (Koichi Sasada) via ruby-core" Date: 2025-08-27T18:15:42+00:00 Subject: [ruby-core:123087] [Ruby Feature#21550] Ractor.sharable_proc/sharable_lambda to make sharable Proc object Issue #21550 has been updated by ko1 (Koichi Sasada). I made a patch to disallow any outer variables and I found that I can't write ostruct case: ```ruby # ostruct-0.6.3/lib/ostruct.rb def new_ostruct_member!(name) # :nodoc: unless @table.key?(name) || is_method_protected!(name) if defined?(::Ractor.shareable_proc) getter_proc = Ractor.shareable_proc { @table[name] } setter_proc = Ractor.shareable_proc {|x| @table[name] = x} elsif defined?(::Ractor) getter_proc = nil.instance_eval{ Proc.new { @table[name] } } setter_proc = nil.instance_eval{ Proc.new {|x| @table[name] = x} } ::Ractor.make_shareable(getter_proc) ::Ractor.make_shareable(setter_proc) else getter_proc = Proc.new { @table[name] } setter_proc = Proc.new {|x| @table[name] = x} end define_singleton_method!(name, &getter_proc) define_singleton_method!("#{name}=", &setter_proc) end end ``` So I think it is needed to allow the way to access outer variables. Option 2 (allow accesses only on lexical blocks) is rejected by Matz, so I want to introduce 1 or 3. 1 is better for me... ---------------------------------------- Feature #21550: Ractor.sharable_proc/sharable_lambda to make sharable Proc object https://bugs.ruby-lang.org/issues/21550#change-114401 * Author: ko1 (Koichi Sasada) * Status: Open * Assignee: ko1 (Koichi Sasada) * Target version: 3.5 ---------------------------------------- Let's introduce a way to make a sharable Proc. * `Ractor.shareable_proc(self: nil, &block)` makes proc. * `Ractor.shareable_lambda(self: nil, &block)` makes lambda. See also: https://bugs.ruby-lang.org/issues/21039 ## Background ### Motivation Being able to create a shareable Proc is important for Ractors. For example, we often want to send a task to another Ractor: ```ruby worker = Ractor.new do while task = Ractor.receive task.call(...) end end task = (sharable_proc) worker << task task = (sharable_proc) worker << task task = (sharable_proc) worker << task ``` There are various ways to represent a task, but using a Proc is straightforward. However, to make a Proc shareable today, self must also be shareable, which leads to patterns like: ```ruby nil.instance_eval{ Proc.new{ ... } } ``` This is noisy and cryptic. We propose dedicated methods to create shareable Proc objects directly. ## Specification * `Ractor.shareable_proc(self: nil, &block)` makes a proc. * `Ractor.shareable_lambda(self: nil, &block)` makes a lambda. Both methods create the Proc/lambda with the given self and make the resulting object shareable. (changed) Accessing outer variables are not allowed. An error is raised at the creation. More about outer-variable handling are discussed below. In other words, from the perspective of a shareable Proc, captured outer locals are read���only constants. This proposal does not change the semantics of Ractor.make_shareable() itself. ## Discussion about outer local variables [Feature #21039] discusses how captured variables should be handled. I propose two options. ### 0. Disallow accessing to the outer-variables It is simple and no confusion. ### 1. No problem to change the outer-variable semantics @Eregon noted that the current behavior of `Ractor.make_shareable(proc_obj)` can surprise users. While that is understandable, Ruby already has similar *surprises*. For instance: ```ruby RSpec.describe 'foo' do p self #=> RSpec::ExampleGroups::Foo end ``` Here, `self` is implicitly replaced, likely via `instance_exec`. This can be surprising if one does not know self can change, yet it is accepted in Ruby. We view the current situation as a similar kind of surprise. ### 2. Enforce a strict rule for non���lexical usage The difficulty is that it is hard to know which block will become shareable unless it is lexically usage. ```ruby # (1) On this code, it is clear that the block will be shareable block: a = 42 Ractor.sharable_proc{ p a } # (2) On this code, it is not clear that the block becomes sharable or not get path do p a end # (3) On this code, it has no problem because get '/hello' do "world" end ``` The idea is to allow accessing captured outer variables only for lexically explicit uses of `Ractor.shareable_proc` as in (1), and to raise an error for non���lexical cases as in (2). So the example (3) is allowed if the block becomes sharable or not. The strict rule is same as `Ractor.new` block rule. ### 3. Adding new rules (quoted from https://bugs.ruby-lang.org/issues/21550#note-7) Returning to the issue: we want a way to express that, within a block, an outer variable is shadowed while preserving its current value. We already have syntax to shadow an outer variable using `|i; a|`, where `a` is shadowed in the block and initialized to `nil` (just like a normal local variable). ```ruby a = 42 pr = proc{|;a| p a} a = 43 pr.call #=> nil ``` What if we instead initialized the shadowed variable to the outer variable's current value? ```ruby a = 42 pr = proc{|;a| p a} a = 43 pr.call #=> 42 ``` For example, we can write the port example like that: ```ruby port = Ractor::Port.new Ractor.new do |;port| port << ... end ``` and it is better (shorter). Maybe only few people know this spec and I checked that there are few lines in rubygems (78 cases in 3M files)(*1). So I think there is a few compatibility impact. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/