What's the Difference Between Proc and Block in Ruby?

In Ruby, both procs and blocks have a similar purpose — i.e. they allow you to group and execute lines of code later on. However, they differ in terms of the following:

  1. Type;
  2. Functionality;
  3. Way of Invocation;
  4. Accepting Arguments;
  5. Return Behavior.

Type

Procs and blocks are of a different type:

Functionality

Procs and blocks are functionally different:

  • Procs are objects that can be stored in variables, passed as arguments to methods, and returned as values:
    # procs can be passed as arguments to methods
    def exec_proc(proc)
      proc.call
    end
    
    # procs can be stored in variables
    my_proc = Proc.new { puts "Hello World!" }
    
    exec_proc(my_proc)  #=> "Hello World!"
    
    # procs can be returned as values from methods
    def create_proc
      Proc.new { puts "Hello World!" }
    end
    
    my_proc = create_proc
    my_proc.call  #=> "Hello World!"
    
  • Blocks are not standalone objects and can only be directly passed to methods for immediate invocation:
    # using a block for immediate invocation
    puts 2.times { puts "Hello!" } #=> "Hello!Hello!"
    
    # passing a block to a method for immediate invocation
    def exec_block
      yield
    end
    
    exec_block { puts "Hello World!" } #=> "Hello World!"
    

Way of Invocation

Procs and blocks are invoked in different ways:

  • Procs can be explicitly invoked using the Proc#call method:
    my_proc = Proc.new { | name | puts "Hello, #{name}!" }
    my_proc.call("John")  #=> Hello, John!
    
  • Blocks are implicitly invoked by using them with methods that yield control to them:
    def exec_block
      yield("John")
    end
    
    exec_block { | name | puts "Hello, #{name}!" } #=> "Hello, John!"
    

Accepting Arguments

Procs can accept arguments and parameters, whereas blocks cannot:

  • When defining a Proc, you can specify parameters within its block argument list:
    my_proc = Proc.new { | x, y | puts "Sum: #{x + y}" }
    my_proc.call(3, 4)  #=> "Sum: 7"
    
  • Blocks don't have explicit parameter definitions; instead they receive arguments from the method they are associated with:
    def calculate
      yield(3, 4)
    end
    
    # block without explicit parameters, receives arguments from the method
    calculate { | x, y | puts "Sum: #{x + y}" } #=> "Sum: 7"
    

Return Behavior

Blocks implicitly return their value to the calling method, while procs have explicit return statements:

  • Procs can use the return keyword to terminate execution and return a value:
    def find_first_even_num(numbers)
      proc = Proc.new do |num|
        return num if num.even?
      end
    
      numbers.each(&proc)
    end
    
    result = find_first_even_num([1, 3, 4, 5, 6])
    puts result  #=> 4
    
  • Blocks' result is passed back to the calling method when they complete execution:
    def find_first_even_num(numbers)
      numbers.each do | num |
        next if num.odd?
        yield num
        break
      end
    end
    
    find_first_even_num([1, 3, 4, 5, 6]) do | result |
      puts result  #=> 4
    end
    

This post was published by Daniyal Hamid. Daniyal currently works as the Head of Engineering in Germany and has 20+ years of experience in software engineering, design and marketing. Please show your love and support by sharing this post.