LaunchKit
All quizzes
Ruby 212 views

When upcase! returns nil mid-chain

Question

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

Explanation

TL;DR

String#upcase! mutates the receiver in place and returns it, but only when a change was actually made. "EURUKO" is already uppercase, so upcase! returns nil, and calling chars on nil raises NoMethodError.

Step by step

The return-value rule shows up as soon as you call upcase! twice on the same string:

str = "hello"

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

The first call changes the string and returns it. The second call has nothing to do, so it returns nil even though str itself is still "HELLO".

That is exactly what breaks the chain in the snippet:

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

begin
  "EURUKO".upcase!.chars
rescue NoMethodError => e
  e.class # => NoMethodError
end

"Euruko".upcase! performs a change and returns the string, so chars works. "EURUKO".upcase! performs no change, returns nil, and nil has no chars method.

Avoiding the issue

Do not chain on a mutating bang method. Either split the mutation from the read:

str = "EURUKO"

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

or use the non-destructive version, which always returns a new string:

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

Edge cases

The nil-on-no-change convention covers the String mutators (upcase!, downcase!, gsub!, sub!, strip!, squeeze!, ...) and some Array ones (uniq!, compact!, flatten!, reject!), but it is not universal. Array#sort!, Array#map!, and String#replace always return the receiver:

[1, 2, 3].sort! # => [1, 2, 3]
[1, 2].map! { |x| x } # => [1, 2]

Check the docs for each bang method before chaining on it; the ! only signals "more dangerous than the non-bang version", not a uniform return-value contract.

Share this quiz

Comments

No comments yet. Be the first.

Only used to confirm and publish your comment. Never shown publicly, never shared.

Markdown: **bold**, `code`, ```fenced blocks```, > quotes, [links](url). HTML and images are not rendered.