LaunchKit
All quizzes
Ruby 25 views

When gsub gets both a replacement and a block

Question

What is the return value of the following Ruby code?

"aaoo".gsub("a") {|c| c.upcase} # => "AAoo"

"aaoo".gsub("a", "o") {|c| c.upcase} # => ???

The correct answer is

Explanation

TL;DR

String#gsub accepts either a replacement argument or a block, and when it receives both, the replacement argument wins. The block is silently ignored, no warning, no error. So every "a" is replaced with "o" and the result is "oooo".

Step by step

With only a block, gsub calls it once per match and substitutes the block's return value:

"aaoo".gsub("a") { |c| c.upcase } # => "AAoo"

With only a replacement argument, gsub substitutes it for every match:

"aaoo".gsub("a", "o") # => "oooo"

With both, the replacement argument takes precedence and the block never runs:

called = false
"aaoo".gsub("a", "o") { |c| called = true; c.upcase } # => "oooo"
called # => false

This matches the documented signatures of gsub: gsub(pattern, replacement) and gsub(pattern) { |match| ... } are two distinct forms, and the two-argument form does not yield.

Edge cases

The same precedence rule applies to sub, gsub!, and sub!. Note that this is the opposite of methods like Hash#fetch, which also accepts a default argument plus a block but prefers the block, and at least warns about the conflict (warning: block supersedes default value argument):

{}.fetch(:a, 1) { 2 } # => 2

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.