{"record":{"id":"a9998089b4f89bea","repo":"ruby-concurrency/concurrent-ruby","slug":"can-t-dump-hash-with-default-proc","errorCode":null,"errorMessage":"can't dump hash with default proc","messagePattern":"can't dump hash with default proc","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/map.rb","lineNumber":306,"sourceCode":"\n    # Is map empty?\n    # @return [true, false]\n    def empty?\n      each_pair { |k, v| return false }\n      true\n    end unless method_defined?(:empty?)\n\n    # The size of map.\n    # @return [Integer] size\n    def size\n      count = 0\n      each_pair { |k, v| count += 1 }\n      count\n    end unless method_defined?(:size)\n\n    # @!visibility private\n    def marshal_dump\n      raise TypeError, \"can't dump hash with default proc\" if @default_proc\n      h = {}\n      each_pair { |k, v| h[k] = v }\n      h\n    end\n\n    # @!visibility private\n    def marshal_load(hash)\n      initialize\n      populate_from(hash)\n    end\n\n    undef :freeze\n\n    # @!visibility private\n    def inspect\n      format '%s entries=%d default_proc=%s>', to_s[0..-2], size.to_s, @default_proc.inspect\n    end\n","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/map.rb#L288-L324","documentation":"Concurrent::Map implements Marshal support via marshal_dump (map.rb:304-309). If the Map was created with a default proc (Concurrent::Map.new { |m, k| ... }), the proc cannot be serialized, so marshal_dump proactively raises TypeError (\"can't dump hash with default proc\"), matching Ruby's Hash behavior. The entries are not the problem; the unserializable proc is.","triggerScenarios":"Marshal.dump(Concurrent::Map.new { |m, k| 0 }); a Map created with a default_proc passed through Marshal.dump, DRb, or fork-based IPC; a caching layer (Rails cache, Sidekiq job arguments) serializing an object graph that contains a default-proc Map.","commonSituations":"Counter/memoization maps built with default-proc blocks and then shipped to a background job; deep state objects containing a Concurrent::Map dumped for persistence; DRb distribution of shared state.","solutions":["Do not build the Map with a default proc: create it plain and read with map.fetch(k) { default }.","Convert to a plain Hash before dumping: map.each_pair.with_object({}) { |(k, v), h| h[k] = v }.","If the default proc is required, exclude the Map from Marshal payloads and persist only its entries."],"exampleFix":"# before\nstats = Concurrent::Map.new { |m, k| m[k] = 0 }\nMarshal.dump(stats) # TypeError: can't dump hash with default proc\n\n# after\nstats = Concurrent::Map.new\nstats.fetch(k) { stats[k] = 0 }\nMarshal.dump(stats.each_pair.with_object({}) { |(k, v), h| h[k] = v })","handlingStrategy":"validation","validationCode":"# Track whether the Map carries a default proc and dump entries, not the Map\nhas_default_proc = true/false # known at construction time\npayload = has_default_proc ? map.each_pair.with_object({}) { |(k, v), h| h[k] = v } : map\nMarshal.dump(payload)","typeGuard":null,"tryCatchPattern":"begin\n  Marshal.dump(map)\nrescue TypeError\n  Marshal.dump(map.each_pair.with_object({}) { |(k, v), h| h[k] = v })\nend","preventionTips":["Avoid default procs on Maps destined for serialization; use fetch with a block.","Dump plain Hash snapshots, never live concurrency primitives.","Keep Concurrent::Map instances out of job arguments and cached payloads."],"tags":["ruby","serialization","marshal","concurrent-map","default-proc"],"backgroundTag":"marshal-dump-unsupported-type","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}