
Issue #21359 has been updated by Eregon (Benoit Daloze). ioquatix (Samuel Williams) wrote in #note-2:
Yes, in Async, I want to set the cause of an exception before raising it later on a fiber.
#21360 would be enough for that, although you'd need to wrap the exception + cause-to-be in some extra object.
Additionally, serialisation and deserialisation of exceptions is almost impossible without being able to set the cause, e.g. any kind of Ruby RPC.
WDYM by almost impossible? Causes can't be cyclic so that's not an issue (which BTW means we would need to check that in this assignment method). `Marshal` can do it I guess. And if not `raise self, cause: value` + `rescue` but I agree that's hacky and inefficient. Serializing exceptions properly without Marshal is probably quite hard yes, not only about the cause but also the internal backtrace representation, the `backtrace_locations` objects, other internal state for core exceptions that can't always be set in constructor, etc. But is there a need for that? ---------------------------------------- Feature #21359: Introduce `Exception#cause=` for Post-Initialization Assignment https://bugs.ruby-lang.org/issues/21359#change-113387 * Author: ioquatix (Samuel Williams) * Status: Open * Assignee: ioquatix (Samuel Williams) ---------------------------------------- Ruby currently allows an exception’s `cause` to be explicitly set **only at the time of raising** using `raise ..., cause: ...`. However, there are valid use cases where it would be convenient to set the cause when creating an exception. ## The Problem Ruby supports exception chaining via the `cause:` keyword during a `raise`, like so: ```ruby raise StandardError.new("failure"), cause: original_exception ``` ## Proposed Solution Introduce `Exception#cause=` as a public setter method on `Exception`, allowing post-initialization assignment of the cause: ```ruby cause = RuntimeError.new("low-level error") error = StandardError.new("higher-level error") error.cause = cause ``` It would be semantically equivalent to the following implementation: ```ruby error = StandardError.new("error") cause = RuntimeError.new("cause") class Exception def cause= value backtrace = self.backtrace_locations raise self, cause: value rescue Exception self.set_backtrace(backtrace) end end p error.cause # nil error.cause = cause p error.cause # => #<RuntimeError: cause> ``` ### Benefits * Simplifies creation of rich exception objects in frameworks, tools, and tests. * Enables structured error chaining in asynchronous and deferred execution environments. * Avoids misuse of `raise`/`rescue` for control flow and metadata setting. -- https://bugs.ruby-lang.org/