Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the ruby equivalent of Go defer?

Tags:

ruby

go

deferred

I am a new to Ruby and I am working on a project which involves using this. Go offers the defer statement, and I wanted to know how I could replicate that function in ruby.

Example:

dst, err := os.Create(dstName)
if err != nil {
    return
}
defer dst.Close()
like image 558
reassesad Avatar asked Jun 15 '16 07:06

reassesad


2 Answers

There are no proper equivalents to the defer statement in ruby, however if you want to make sure that a specific code block is executed, you can use the ensure statement. The difference is that you cannot stack code blocks like defer does, but the result is the same.

In a block

begin
  # ...
ensure
  # This code will be executed even if an exception is thrown
end

In a method

def foo
  # ...
ensure
  # ...
end

Object#ensure Marks the final, optional clause of a begin/end block, generally in cases where the block also contains a rescue clause. The code in the ensure clause is guaranteed to be executed, whether control flows to the rescue block or not.

like image 69
basgys Avatar answered Sep 17 '22 15:09

basgys


It doesn't have such a statement, but you can use metaprogramming to get this behavior.

module Deferable
  def defer &block
    @defered_methods << block
  end

  def self.included(mod)
    mod.extend ClassMethods
  end

  module ClassMethods
    def deferable method
      original_method = instance_method(method)
      define_method(method) do |*args|
        @@defered_method_stack ||= []
        @@defered_method_stack << @defered_methods
        @defered_methods = []
        begin
          original_method.bind(self).(*args)
        ensure
          @defered_methods.each {|m| m.call }
          @defered_methods = @@defered_method_stack.pop
        end
      end
    end
  end
end

class Test
  include Deferable

  def test
    defer { puts "world" }
    puts "hello"
  end

  def stacked_methods str
    defer { puts "and not broken" }
    defer { puts "at all" }
    puts str
    test
  end

  def throw_exception
    defer { puts "will be executed even if exception is thrown" }
    throw Exception.new
  end

  deferable :test
  deferable :stacked_methods
  deferable :throw_exception
end

Example calls:

t = Test.new
t.test

# -> Output:
# hello
# world

t.stacked_methods "stacked methods"

# -> Output:
# stacked methods
# hello
# world
# and not broken
# at all

t.throw_exception
# -> Output:
# will be executed even if exception is thrown
# deferable.rb:45:in `throw': uncaught throw #<Exception: Exception> (UncaughtThrowError)
#         from deferable.rb:45:in `throw_exception'
#         from deferable.rb:18:in `call'
#         from deferable.rb:18:in `block in deferable'
#         from deferable.rb:59:in `<main>'
like image 25
AlexN Avatar answered Sep 21 '22 15:09

AlexN