Skip to content

Conversation

@osyoyu
Copy link
Contributor

@osyoyu osyoyu commented Dec 9, 2025

This patch makes procs which meet the following condition automatically be Ractor shareable.

  • The proc itself is frozen.
  • The proc's self is Ractor shareable.

Consider this code:

class C
  PROC = proc { p 1 }.freeze
end

Ractor.new { C::PROC.call }.value

In this case, C::PROC will be automatically Ractor shareable, since its self is C, which is also Ractor shareable.

Usecase:

The main usecase in mind is procs/lambdas in class-level constants. Some libraries store procs in constants as a convenient place for library-wide logic. Those procs usually do not access unshareable state, thus conceptually safe to be shared across Ractors. However, the current limitation completely blocks this.

Examples may be found in ruby/ruby:

class Pathname
 SAME_PATHS = if File::FNM_SYSCASE.nonzero?
   # Avoid #zero? here because #casecmp can return nil.
   proc {|a, b| a.casecmp(b) == 0}
 else
   proc {|a, b| a == b}
 end
end

https://github.com/search?q=repo%3Aruby%2Fruby%20%2F(%3F-i)%5BA-Z%5D%20%3D%20(proc%7Clambda)%2F&type=code

More examples can be found in public code.

https://github.com/search?q=language%3Aruby+%2F%28%3F-i%29%5BA-Z%5D+%3D+%28proc%7Clambda%29%2F&type=code

It can be observed that a good portion of these do not access unshareable state.

Appending .freeze would be much more acceptable than redefining using Ractor.shareable_proc, which is a Ruby 4.0-only feature.

[Feature #21767]

This patch makes procs which meet the following condition automatically
be Ractor shareable.

- The proc itself is frozen.
- The proc's `self` is Ractor shareable.

Consider this code:

    class C
      PROC = proc { p 1 }.freeze
    end

    Ractor.new { C::PROC.call }.value

In this case, C::PROC will be automatically Ractor shareable, since its
self is `C`, which is also Ractor shareable.

Usecase:

The main usecase in mind is procs/lambdas in class-level constants. Some libraries store procs in constants as a convenient place for library-wide logic. Those procs usually do not access unshareable state, thus conceptually safe to be shared across Ractors. However, the current limitation completely blocks this.

Examples may be found in ruby/ruby:

    class Pathname
     SAME_PATHS = if File::FNM_SYSCASE.nonzero?
       # Avoid #zero? here because #casecmp can return nil.
       proc {|a, b| a.casecmp(b) == 0}
     else
       proc {|a, b| a == b}
     end
    end

https://github.com/search?q=repo%3Aruby%2Fruby%20%2F(%3F-i)%5BA-Z%5D%20%3D%20(proc%7Clambda)%2F&type=code

More examples can be found in public code.

https://github.com/search?q=language%3Aruby+%2F%28%3F-i%29%5BA-Z%5D+%3D+%28proc%7Clambda%29%2F&type=code

It can be observed that a good portion of these do not access unshareable state.

Appending `.freeze` would be much more acceptable than redefining using `Ractor.shareable_proc`, which is a Ruby 4.0-only feature.

[Feature #21767]
@osyoyu osyoyu force-pushed the ractor-auto-shareable-procs branch from 33aa1dc to d9d5cc0 Compare December 15, 2025 15:16
@launchable-app

This comment has been minimized.

@osyoyu osyoyu force-pushed the ractor-auto-shareable-procs branch 2 times, most recently from 726db9d to 410dd0c Compare December 16, 2025 06:43
@osyoyu osyoyu marked this pull request as ready for review December 16, 2025 09:19
@luke-gruber
Copy link
Contributor

luke-gruber commented Dec 16, 2025

This raises ArgumentError:

class C
  a =  1
  PROC = -> { a+=1; a }.freeze
  p Ractor.shareable?(PROC)
end

I think it would be good to add a test case that exercises trying to use a frozen proc with shareable self that accesses outer variables from another ractor. Also one that calls Ractor.shareable?(p) for a proc like this (like above), and maybe also Ractor.shareable_proc(&PROC) and Ractor.make_shareable(PROC).

Having said that, I'm not sure if I'm +1 on this due to the concerns shared by Eregon on the issue tracker.

@osyoyu
Copy link
Contributor Author

osyoyu commented Dec 19, 2025

@luke-gruber Do you mean your script should raise an ArgumentError ? It does not raise on neither master nor this branch.

Calling Ractor.new { C::PROC.call }.value does raise a Ractor::IsolationError on master, but this branch happily executes that, so I'll check that (it obviously should raise).

@nobu nobu requested a review from ko1 December 20, 2025 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants