dry-rb / dry-rb/dry-validation
Ractor incompatibility: contracts, schemas, types, and results cannot be used from non-main Ractors on Ruby 4.0
- Dominant language
- Ruby
- Stars
- 1.4k
- Forks
- 195
- PR merge metrics
- No merged PRs in 30d
Description
`dry-validation` (1.11.1) and its underlying dependencies (`dry-schema`, `dry-types`, `dry-initializer`) cannot be used inside [Ractors](https://docs.ruby-lang.org/en/master/Ractor.html) on Ruby 4.0. No operation works — instantiating contracts, calling them, defining schemas, looking up types, or even passing a validation result into a Ractor.
### Environment
- **Ruby version:** 4.0.3 (2026-04-21)
- **dry-validation:** 1.11.1
- **dry-schema:** 1.16.0
- **dry-types:** 1.9.1
- **dry-initializer:** 3.2.0
- **OS:** Linux x86_64
## Reproduction
Save as `dry_validation_ractor_poc.rb` and run with `ruby dry_validation_ractor_poc.rb`:
```ruby
#!/usr/bin/env ruby
# frozen_string_literal: true
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
gem "dry-validation", "~> 1.10"
end
require "dry-validation"
version = Gem.loaded_specs["dry-validation"]&.version
puts "Ruby #{RUBY_VERSION}, dry-validation #{version}"
puts "-" * 50
class UserContract < Dry::Validation::Contract
params do
required(:name).filled(:string)
required(:age).filled(:integer, gt?: 0)
end
rule(:name) { key.failure("too short") if value.length < 2 }
end
contract = UserContract.new
tests = {
"Contract.new in Ractor" => -> { Ractor.new { UserContract.new }.value },
"contract.call in Ractor" => -> { Ractor.new(contract) { |c| c.call(name: "Alice", age: 30) }.value },
"Define contract in Ractor" => -> {
Ractor.new {
Class.new(Dry::Validation::Contract) { params { required(:x).filled(:string) } }.new.call(x: "hi")
}.value
},
"Dry::Types[] in Ractor" => -> { Ractor.new { Dry::Types["strict.string"] }.value },
"Dry::Schema.Params in Ractor"=> -> { Ractor.new { Dry::Schema.Params { required(:a).filled(:string) } }.value },
"Pass result across Ractor" => -> { Ractor.new(contract.call(name: "Alice", age: 30)) { |r| r.success? }.value },
}
tests.each do |name, test|
print "#{name}: "
begin
test.call
puts "OK"
rescue Ractor::RemoteError => e
puts "FAILED — #{e.cause.class}: #{e.cause.message}"
rescue => e
puts "FAILED — #{e.class}: #{e.message}"
end
end
```
### Actual output
```
Ruby 4.0.3, dry-validation 1.11.1
--------------------------------------------------
Contract.new in Ractor: FAILED — RuntimeError: defined with an un-shareable Proc in a different Ractor
contract.call in Ractor: FAILED — TypeError: allocator undefined for Proc
Define contract in Ractor: FAILED — Ractor::IsolationError: can not get unshareable values from instance variables of classes/modules from non-main Ractors (@dry_initializer from Dry::Validation::Contract)
Dry::Types[] in Ractor: FAILED — Ractor::IsolationError: can not get unshareable values from instance variables of classes/modules from non-main Ractors (@type_map from Dry::Types)
Dry::Schema.Params in Ractor: FAILED — RuntimeError: defined with an un-shareable Proc in a different Ractor
Pass result across Ractor: FAILED — TypeError: allocator undefined for Proc
```
### Expected behavior
At minimum, pre-built contracts should be callable from non-main Ractors, and results should be transferable across Ractor boundaries. Ideally, defining new contracts and looking up types should also work.
Contributor guide
Assessment
This issue has not been assessed yet.