Project

General

Profile

Feature #21550

Updated by Eregon (Benoit Daloze) 15 days ago

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) (sharable_proc) 
 worker << task 

 task = (shareable_proc) (sharable_proc) 
 worker << task 

 task = (shareable_proc) (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.shareable_proc{ 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. 


Back