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
-
["E", "u", "r", "u", "k", "o"] -
["e", "u", "r", "u", "k", "o"] -
["E", "U", "R", "U", "K", "O"] -
It raises
NoMethodErrorCorrect
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.