From: "Eregon (Benoit Daloze) via ruby-core" Date: 2025-08-28T09:02:06+00:00 Subject: [ruby-core:123107] [Ruby Feature#21550] Ractor.shareable_proc/shareable_lambda to make sharable Proc object Issue #21550 has been updated by Eregon (Benoit Daloze). Yes, that's the idea. Yes it's feasible via static analysis (e.g. in `compile.c`). It would be somewhat conservative but I think that's good enough, I'll give more details in my proposal. I think a possible simple way is to use "if a captured variable is assigned more than once, `Ractor.sharable_proc` is `Ractor::IsolationError` on a block using that captured variable". ---------------------------------------- Feature #21550: Ractor.shareable_proc/shareable_lambda to make sharable Proc object https://bugs.ruby-lang.org/issues/21550#change-114424 * 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 = (shareable_proc) worker << task task = (shareable_proc) worker << task task = (shareable_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.shareable_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/