module ReturnBang

ReturnBang is allows you to perform non-local exits from your methods. One potential use of this is in a web framework so that a framework-provided utility methods can jump directly back to the request loop.

Since providing just non-local exits is insufficient for modern Ruby development, full exception handling support is also provided via raise!, rescue! and ensure!. This exception handling support completely bypasses Ruby’s strict begin; rescue; ensure; return handling.

require ‘return_bang’ gives you a module you may include only in your application or library code. require ‘return_bang/everywhere’ includes ReturnBang in Object, so it is only recommended for application code use.

Methods

#return_here is used to designate where execution should be resumed. Return points may be arbitrarily nested. return! resumes at the previous resume point, return_to returns to a named return point.

raise! is used to indicate an exceptional situation has occurred and you would like to skip the rest of the execution.

rescue! is used to rescue exceptions if you have a way to handle them.

ensure! is used when you need to perform cleanup where an exceptional situation may occur.

Example

include ReturnBang

def framework_loop
  loop do
    return_here do
      # setup this request

      ensure! do
        # clean up this request
      end

      rescue! FrameworkError do
        # display framework error
      end

      rescue! do
        # display application error
      end

      user_code
    end
  end
end

def user_code
  user_utility_method

  other_condition = some_more code

  return! if other_condition

  # rest of user method
end

def user_utility_method
  raise! "there was an error" if some_condition

  # rest of utility method
end

Constants

VERSION

Public Instance Methods

ensure!(&block) click to toggle source

Adds an ensure block that will be run when exiting this #return_here block.

ensure! blocks run in the order defined and can be added at any time. If an exception is raised before an ensure! block is encountered, that block will not be executed.

Example:

return_here do
  ensure! do
    # this ensure! will be executed
  end

  raise! "uh-oh!"

  ensure! do
    # this ensure! will not be executed
  end
end
# File lib/return_bang.rb, line 217
def ensure! &block
  _return_bang_stack.push [:ensure, block]
end
raise!(*args) click to toggle source

Raises an exception like Kernel#raise.

ensure! blocks and rescue! exception handlers will be run as the exception is propagated up the stack.

# File lib/return_bang.rb, line 227
def raise! *args
  Thread.current[:current_exception] = _make_exception args

  type, = _return_bang_stack.first

  _, final = _return_bang_stack.shift if type == :return

  frames = _return_bang_stack.dup

  _return_bang_stack.clear

  _return_bang_cleanup frames

  final.call if final
end
rescue!(*exceptions, &block) click to toggle source

Rescues exceptions raised by raise! and yields the exception caught to the block given.

If no exceptions are given, StandardError is rescued (like the rescue keyword).

Example:

return_here do
  rescue! do |e|
    puts "handled exception #{e.class}: #{e}"
  end

  raise! "raising an exception"
end
# File lib/return_bang.rb, line 260
def rescue! *exceptions, &block
  exceptions = [StandardError] if exceptions.empty?

  _return_bang_stack.push [:rescue, block, exceptions]
end
return!(value = nil) click to toggle source

Returns to the last return point in the stack. If no return points have been registered a NonLocalJumpError is raised. value is returned at the registered return point.

# File lib/return_bang.rb, line 271
def return! value = nil
  raise NonLocalJumpError, 'nowhere to return to' if
    _return_bang_stack.empty?

  _, continuation, = _return_bang_stack.reverse.find do |type,|
    type == :return
  end

  _return_bang_unwind_to continuation

  continuation.call value
end
return_here(name = nil) { || ... } click to toggle source

Registers a return point to jump back to. If a name is given #return_to can jump here.

# File lib/return_bang.rb, line 288
def return_here name = nil
  raise ArgumentError, "#{name} is already registered as a return point" if
    _return_bang_names.include? name

  value = callcc do |cc|
    _return_bang_names[name] = _return_bang_stack.length if name
    _return_bang_stack.push [:return, cc]

    begin
      yield
    ensure
      _return_bang_unwind_to cc
    end
  end

  if exception = Thread.current[:current_exception] then
    Thread.current[:current_exception] = nil

    raise exception
  end

  # here is where the magic happens
  unwind_to = Thread.current[:unwind_to]

  return! value if unwind_to and _return_bang_stack.length > unwind_to

  return value
end
return_to(name, value = nil) click to toggle source

Returns to the return point name. value is returned at the registered return point.

# File lib/return_bang.rb, line 321
def return_to name, value = nil
  unwind_to = _return_bang_names.delete name

  raise NonLocalJumpError, "return point :nonexistent was not set" unless
    unwind_to

  Thread.current[:unwind_to] = unwind_to

  return! value
end