In Ruby, what are the forms of raise with a message, a class or an instance, and what does a bare raise do inside rescue?
answer
- a string alone means RuntimeError
- class plus message calls exception
- an instance is raised as is
- bare raise re-raises $!
- fail is an alias
basics
~10 sraise "msg" raises a RuntimeError; raise Klass, "msg" builds the error through Klass.exception, which calls new; raise an_instance raises that object. A bare raise inside rescue re-raises the current exception with its original backtrace.
solid answer
~40 s`Kernel#raise` (alias `fail`) takes one to three arguments plus a `cause:` keyword. With a string only, it raises a `RuntimeError` with that message. With a class, `raise ParseError` or `raise ParseError, "bad row"`, Ruby calls `ParseError.exception(message)`, which for a class is `new`, so the class's `initialize` receives the message. With an instance, `raise error` raises that object; `raise error, "other text"` calls `error.exception("other text")`, which returns a copy with the new message. An optional third argument replaces the backtrace. Inside a rescue clause, a bare `raise` re-raises the exception being handled, the same object with its original backtrace; outside any rescue it raises a `RuntimeError`. Anything that is not an exception class or object, and has no `exception` method, gives `TypeError`.
code
ruby · 13 linesclass ParseError < StandardError; end
raise "bad row" # RuntimeError: bad row
raise ParseError # ParseError: ParseError
raise ParseError, "bad row 12" # ParseError: bad row 12
raise ParseError.new("bad row 12") # the same, instance form
begin
Integer("12x")
rescue ArgumentError => e
warn "parse failed: #{e.message}"
raise # same object, original backtrace
endgo deeper
Recall the four everyday forms and that a bare raise inside rescue re-raises the same error.
Explain the exception protocol behind raise: class.exception is new, instance.exception returns self or a copy, and the result must be an Exception.
Use re-raising deliberately so backtraces stay pointed at the real failure, and prefer specific classes over raise with a bare string in library code.
Set conventions for raise style and error classes across a codebase so every raise site can be rescued precisely and linted consistently.
## The calling forms `Kernel#raise` signals an exception. Its documented call sequences are `raise(exception, message = exception.to_s, backtrace = nil, cause: $!)` and `raise(message = nil, cause: $!)`. `fail` is the same method under another name. | Form | What is raised | |---|---| | `raise` inside a rescue clause | the exception currently being handled (`$!`), unchanged | | `raise` outside any rescue | a new `RuntimeError` | | `raise "Carrier unreachable"` | `RuntimeError` with that message | | `raise ParseError` | `ParseError.new`, message defaults to the class name | | `raise ParseError, "bad row 12"` | `ParseError.new("bad row 12")` | | `raise ParseError.new("bad row 12")` | that instance | | `raise error, "new text"` | a copy of `error` with the new message | | `raise ParseError, "msg", backtrace` | as above, with the given backtrace instead of the current stack | ## What raise does with its first argument Under the hood, `raise` follows a small protocol: 1. If the only argument is a string, it builds a `RuntimeError`. 2. Otherwise it calls **`exception`** on the first argument, passing the message if one was given. `Exception.exception` on a class is simply `new`. `Exception#exception` on an instance returns `self` when called with no message, and a clone with the new message otherwise, leaving the original untouched. 3. It checks that the result is an `Exception`; if the argument had no `exception` method, it raises `TypeError` ("exception class/object expected"). 4. It sets the backtrace from the third argument if given, otherwise from the current call stack, unless the object already has one. 5. It records the **cause**: the `cause:` keyword if given, otherwise `$!`. Because the protocol is duck-typed, any object with an `exception` method returning an exception can be passed to `raise`, so a result or error-report object can be made raisable by defining that one method. ## Bare raise and raise e Inside a rescue clause, these two lines behave the same: - **`raise`** re-raises `$!`, the exception being handled. - **`raise e`**, where `e` is the rescued object, calls `e.exception` with no message, which returns the same object. In both cases the object keeps its **original backtrace**, pointing at where the error first happened, not at the rescue clause. That is what makes the log-and-re-raise pattern safe. The difference between the two appears only when you nest handlers: a bare `raise` always refers to the innermost exception being handled, which may not be the `e` you meant. ## Style conventions - RuboCop's **`Style/RaiseArgs`** defaults to the **exploded** style, `raise ParseError, "msg"`, rather than `raise ParseError.new("msg")`. Its configuration comment calls it a pure preference and expects it to be disabled by default in the next major release. - **`Style/SignalException`** enforces `raise` over `fail` by default, with the same note that the distinction stopped being meaningful long ago. - **`Lint/RaiseException`** flags raising the `Exception` class itself, which escapes every ordinary rescue. ## Choosing a form In a shipping-rates client, `raise "Carrier unreachable"` works but raises a generic `RuntimeError` that callers can only rescue broadly. `raise ShippingRates::CarrierError, "Carrier unreachable"` lets callers rescue exactly that failure. Use the instance form when the error needs keyword fields, since the class form can pass only a message to `new`. ## Mistakes interviewers look for - **Raising `Exception`.** `raise Exception, "x"` produces an error that bare `rescue` and `rescue => e` do not catch; raise a `StandardError` subclass instead. - **Re-raising with a new message by string.** `raise e.message` creates a `RuntimeError` with the old text and loses the original class, so callers' `rescue` clauses for that class stop matching. - **Bare `raise` in a helper.** A `raise` with no arguments inside a method that is sometimes called outside a rescue clause raises a generic `RuntimeError`, which is confusing to debug. - **Passing a non-exception.** `raise 42` or `raise nil` fails with `TypeError`, hiding the intended error. The safe defaults are short: raise a specific class with a message, and inside a rescue clause re-raise with a bare `raise` when you only want to log or clean up.
- Can you raise an object that is not an Exception?Only if it responds to `exception` and that method returns an `Exception`. `raise` calls `exception` on its first argument, so a result or error-report object can define `exception` to build the real error. Passing an object without that method, such as `raise 42`, gives `TypeError` with `exception class/object expected`.
- What does raise error, "new text" do to the original error object?Nothing. `raise` calls `error.exception("new text")`, and `Exception#exception` with a message returns a clone carrying the new message. The clone is raised; the original keeps its message, which matters if the original is stored or logged elsewhere.
- What does a bare raise do when no exception is being handled?It raises a new `RuntimeError`, because there is no `$!` to re-raise. That is almost always a mistake, typically a bare `raise` left in a helper method that is sometimes called outside a rescue clause.
saying these in an interview costs you the question
- raise "message" raises a plain StandardError rather than a RuntimeError
- A bare raise inside rescue resets the backtrace to the rescue line
- raise error, "text" changes the message of the original object
- raise can only take an exception class, never an instance
- fail and raise raise different kinds of exceptions