Bang methods and nil

548 views

What's the return value of this Ruby code?

"Euruko".upcase!.chars # => ["E", "U", "R", "U", "K", "O"]

"EURUKO".upcase!.chars # => ???

The correct answer is

["E", "u", "r", "u", "k", "o"]

["e", "u", "r", "u", "k", "o"]

["E", "U", "R", "U", "K", "O"]

It raises NoMethodError

Unlock Your Ruby Potential

Subscribe to RubyCademy and get free access to all our courses, plus hundreds of fun Ruby cards, quizzes, guides, and tutorials!

Explanation

In Ruby, bang methods (!) like upcase!, gsub!, and others are designed to modify the object in place.

However, they don't always return the object itself. If no modification is needed, these methods return nil, which can lead to unexpected behavior in your code.

Consider this example:

str = "hello"
p str.upcase!  # => "HELLO"
p str.upcase!  # => nil

In the first call, "hello" is converted to "HELLO", so upcase! returns the modified string.

In the second call, since the string is already in uppercase, upcase! does nothing and returns nil.

This behavior can cause issues when chaining methods, as shown here:

"Euruko".upcase!.chars   # => ["E", "U", "R", "U", "K", "O"]
"EURUKO".upcase!.chars   # => NoMethodError

Why Does This Happen?

"Euruko".upcase!

The string "Euruko" is modified to "EURUKO", and upcase! returns the modified string. This allows the subsequent chars method to work, producing ["E", "U", "R", "U", "K", "O"].

"EURUKO".upcase!

Since the string is already in uppercase, upcase! returns nil. When Ruby tries to call chars on nil, it raises a NoMethodError, because nil does not have a chars method.

Avoiding the Issue

To prevent this, you can avoid chaining additional methods :

str = "EURUKO"

str.upcase!
str.chars

Alternatively, you can use the non-destructive version of the method (upcase), which always returns a new string regardless of whether any changes were made:

"EURUKO".upcase.chars  # => ["E", "U", "R", "U", "K", "O"]

Summary

Bang methods like upcase! modify an object in place and return the modified object only if changes are made. If no changes are needed, they return nil.

This can break method chains if you're expecting the object to always be returned, leading to potential NoMethodErrors when nil is encountered unexpectedly.

Unlock Your Ruby Potential

Subscribe to RubyCademy and get free access to all our courses, plus hundreds of fun Ruby cards, quizzes, guides, and tutorials!

RubyCademy ©