Marcus Thornton
Marcus Thornton

Reputation: 6193

How is Array#each implemented in Ruby?

I'm curious about the mechanism of collection and block in Ruby. We can define a class like this:

class Foo
    include Enumerable
    def initialize
        @data = []
    end

    def each
        if block_given?
            @data.each { |e| yield(e) }
        else
            Enumerator.new(self, :each)
        end
    end 
end

I want to know how @data.each can use the block { |e| yield(e) } as the parameter. I searched the implementation of Array#each:

rb_ary_each(VALUE array)
{
    long i;
    volatile VALUE ary = array;

    RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length);
    for (i=0; i<RARRAY_LEN(ary); i++) {
        rb_yield(RARRAY_AREF(ary, i));
    }
    return ary;
}

but this doesn't even seem like a Ruby language implementation. How is Array#each implemented in Ruby?

Edit:

It looks like C code traverses the first to final array element and call ruby yield function with the parameter of the traversed array element. But where is the block passed to rb_ary_each function?

Upvotes: 4

Views: 3671

Answers (2)

Pedro Gabriel Lima
Pedro Gabriel Lima

Reputation: 1172

I may come late to this topic, but I guess that VALUE array is a object/struct instead of an primitive value as int, char, etc. So, what rb_ary_each method is receiving is a pointer/reference to the object/struct of the type VALUE. If that is true (I'm just guessing here for what I know about C) then the block is passed inside the object/struct with the remain arguments.

I'm also guessing that another object/struct should exist to hold the block/proc/lambda, so the VALUE object/struct should hold a pointer/reference to the first one.

If I'm wrong, please, someone correct me! Your question are much more related to how C works than Ruby.

Upvotes: 0

Ajedi32
Ajedi32

Reputation: 48368

Here's a simple Ruby implementation of an each-like method that doesn't rely on the existence of any of Ruby's built-in iterator methods:

class List
  def initialize(arr)
    @arr = arr
  end
  def each
    i = 0
    while i < @arr.length
      yield @arr[i]
      i += 1
    end
  end
end

Usage:

l = List.new(['a', 'b', 'c'])
l.each {|x| puts x}

Output:

a
b
c

Upvotes: 4

Related Questions