Why I love Ruby: blocks
Tags: ruby, crystal
Published: 2022-02-22 08:55:53

---
title: Why I love Ruby: blocks
published: true
description: 
tags: ruby, crystal
series: Why I love Ruby
//cover_image: https://direct_url_to_image.jpg
---

If you already know what are blocks in Ruby you can skip to the "Ruby blocks are not just anonymous functions" section. Otherwise, here's a small tutorial.

# Small introduction to Ruby blocks

Ruby methods can accept a block. Here's the simplest example:

```ruby
def run
  yield
end

run do
  puts "Hello world!"
end
```

In the above example `run` yields. When we call `run` we give it a block (`do` ... `end`) Then the block's contents are executed exactly where `yield` was used.

With this, we can define something a bit more useful:

```ruby
def twice
  yield
  yield
end

twice do
  puts "Hello world!"
end 
```

Because we do `yield` twice, the block gets executed... twice!

This general idea of executing something a number of times can also be achieved like this in Ruby:

```ruby
3.times do
  puts "Hello world!"
end
```

And blocks can receive arguments, so you can also do this:

```ruby
3.times do |i|
  puts "Hello number #{i}!"
end
```

The above outputs this:

```
Hello number 0!
Hello number 1!
Hello number 2!
```

# Ruby blocks are not just anonymous functions

At first glance Ruby blocks are similar to a concept that exists in many languages: passing anonymous functions to functions that, in turn, receive functions and execute them.

Let's try defining a `times` function in Go:

```go
func times(n int, block func(int)) {
	for i := 0; i < n; i++ {
		block(i)
	}
}
```

This function takes an integer `n` and will invoke the given function named `block` that many times.

Here's a full program that uses it:

```go
package main

import "fmt"

func times(n int, block func(int)) {
	for i := 0; i < n; i++ {
		block(i)
	}
}

func main() {
	times(3, func(i int) {
		fmt.Printf("Hello number %v!\n", i)
	})
}
```

The output is the same as the `3.times` Ruby snippet mentioned before.

Aside from having to write a bit more code, and having a bit more noise in it, it might seem that blocks aren't that special in Ruby after all... you can do the above thing in Go, Java, C#, Rust, and many more.

To show that Ruby blocks are different, I came up with this program:

```ruby
def greet_20_numbers
  seconds = Time.now.sec

  20.times do |i|
    puts "Hello number #{i + 1}!"
    if i == seconds
      puts "Oh no, time's up!"
      return
    end
  end
  puts "Bye!"
end

greet_20_numbers
```

The above program doesn't make a lot of sense, but here's how it works:
- It prints 20 times "Hello number ...!" followed by priting "Bye!"
- However, if while printing the above it happens that the number to greet is the number of seconds on the clock, we print "Oh no, time's up!". Note that we don't print "Bye!" in this case because we return from the method.

Let's try to translate the above program to Go:

```go
package main

import "fmt"
import "time"

func times(n int, block func(int)) {
	for i := 0; i < n; i++ {
		block(i)
	}
}

func greet20numbers() {
	now := time.Now()
	second := now.Second()

	times(20, func(i int) {
		fmt.Printf("Hello number %v!\n", i)
		if i == second {
			fmt.Println("Oh, time's up!")
			return
		}
	})
	fmt.Printf("Bye!")
}

func main() {
	greet20numbers()
}
```

Running the above program doesn't work like the Ruby program. Can you spot why?

In Go we also use `return` to exit the function. But this `return` returns from the function we gave to `times`, it doesn't return from `greet20numbers`! In Ruby, `return` returns from the enclosing method, not the enclosing block.

This is the most important difference between Ruby blocks and passing functions around.

For example many languages (C, Java, C#, Go, Rust) have `if`, `while` or `for`. These constructs also have a "block" of sorts where we do things. In the case of `if`, whatever we put inside that `if` gets executed if the condition holds. For `while` it's similar: the "block" gets executed while the condition holds. With `for` you can use more complex conditions. And in all of them if you `return` from inside these "blocks", you return from the enclosing function. But then you can't define other constructs that behave in the same way: you might be able to pass "blocks" around, but if you use `return` then you return from those "blocks", not from the enclosing method.

In Ruby, with blocks, you can create your own constructs that behave like `if`, `while` or `for` would. If you return from inside `3.times do ... end`, you return from the method. If you do:

```ruby
File.open("some_file.txt") do |file|
  file.each_line do |line|
    return if some_condition
  end
end 
```

then that `return` which is nested inside two blocks will return from the enclosing method! Then all of these blocks don't look like anonymous functions anymore, they look like augmented language syntax.

# Give me a break

There's one more thing you can do inside blocks that you can't do with regular anonymous functions from other languages: you can `break` from them.

For example:

```ruby
3.times do |i|
  puts "Hello number #{i}!"

  break if i == 1
end
```

The above will print "Hello number 0!", then "Hello number 1!" and stop.

Just like you can `break` from a `while` in many languages, breaking from loops lets you create your own loops that enjoy all the benefits of regular `while` loops: you can return from them and you can break from them! This further reinforces the notion that these methods look like new language constructs rather than anonymous functions.

Finally, you can call `next` inside a block to go to the next iteration, just like you can call `next` or `continue` inside a `while` in other languages. However, you can do this with anonymous functions: just `return` from them.

# There's more to Ruby blocks

Ruby blocks can also be captured and passed around. But I won't cover that here because that does look like anonymous functions or passing functions around. That said, you can also execute blocks in the context of another object, which again I won't cover here. But all of these things make Ruby blocks even more versatile, useful and fun to use!

# Other languages that have blocks

Of course Crystal has blocks and they can work like Ruby. Here's the sample program in Crystal:

```crystal
def greet_20_numbers
  seconds = Time.local.second
  puts seconds

  20.times do |i|
    puts "Hello number #{i + 1}!"
    if i == seconds
      puts "Oh, it's time to get back to work!"
      return
    end
  end
  puts "Bye!"
end

greet_20_numbers
```

The only difference is that we use `Time.local.second` instead of `Time.now.sec`, but otherwise it works exactly the same as Ruby.

[Kotlin](https://kotlinlang.org/docs/inline-functions.html#non-local-returns) is another language where you can also augment the syntax with methods that receive functions or "blocks" and where using `return` actually returns from the enclosing function. This is great! I think Kotlin is a really good language that, and this is just a guess, took a lot of inspiration from Ruby. That said, [it seems you can't `break` from these like you can in Ruby](https://kotlinlang.org/docs/returns.html#return-to-labels) but there are small workarounds you can use for that. 

# Coming up next

I'll be talking about Ruby's secret algorithms.