RolandXu RolandXu - 2 months ago 5
Ruby Question

How Ruby implements Enumerator#next method?

class MyString
include Enumerable
def initialize(n)
@num = n
def each
i = 0
while i < @num
yield "#{i} within while"
puts "After yield #{i}"
i += 1

s =
a = s.to_enum
puts "first"
puts "second"

My ruby version is 2.2.5, and outputs of codes are


0 within while


After yield 0

1 within while

I think the execution flow is
first>s.each->while->yield->second>jump into while loop

My question is how Enumerator#next method is implemented?

I probably know there are break in block yield invoked, which cause
; however, I don't understand how second can jump back into a while loop.


I don't understand how second can jump back into a while loop.

Magic. Enumerator's (and Fiber's) superpowers.

These two classes were introduced in Ruby 1.9, and share many similarities; in particular, they allow you to do manual co-operative green-threading.

Let's look at fibers first, as they are more basic:

f = do
  puts "A"
  Fiber.yield 1
  puts "B"
  Fiber.yield 2
  puts "C"

puts "First"    # First
puts f.resume   # A
                # 1
puts "Second"   # Second
puts f.resume   # B
                # 2
puts "End"      # End
f.resume        # C
f.resume        # FiberError: dead fiber called

Basically, a fiber is like a thread, but it will pause whenever it yields by Fiber.yield, and resume whenever it is resumed by Fiber#resume. It is implemented in C as basic capability of Ruby, so as a student of Ruby (as opposed to student of Ruby interpreter) you don't need to know how it works, just that it does (just like you need to know IO#read will read a file, but not necessarily how it is implemented in C).

Enumerator is almost the same concept, but adapted for iteration (whereas Fiber is more multi-purpose). In fact, we can write the above almost exactly word-for-word the same with an Enumerator:

e = do |yielder|
  puts "A"
  yielder.yield 1
  puts "B"
  yielder.yield 2
  puts "C"

puts "First"    # First
puts     # A
                # 1
puts "Second"   # Second
puts     # B
                # 2
puts "End"      # End          # C
                # StopIteration: iteration reached an end