Navigating to safe navigation
In December 2013, Ruby 2.3 was released and introduced a safe navigation operator to the language: &.
. The behavior of the operator is identical to ActiveSupport’s try!
:
toast = []
# Both lines below push to the array
toast.try!(:push, '🥑')
toast&.push('🥑')
toast = ""
# Both lines below raise NoMethodError
toast.try!(:push, '🥑')
toast&.push('🥑')
toast = nil
# Both lines below return nil
toast.try!(:push, '🥑')
toast&.push('🥑')
In the Rails projects I work on, try!
is used extensively, so this new operator quickly caught my eye.
Curious to see how &.
is implemented, I looked up the patch and noticed that it’s written entirely in C, unlike try!
which is written in Ruby. Knowing this, I was curious if there is any performance benefit to migrate to &.
, so I threw together a benchmark:
require "benchmark"
require "active_support/all"
Benchmark.bm(14) do |x|
count = 10_000_000
x.report("nil.try!(:length)") do
count.times { nil.try!(:length) }
end
x.report("'a'.try!(:length)") do
count.times { 'a'.try!(:length) }
end
x.report("nil&.length ") do
count.times { nil&.length }
end
x.report("'a'&.length ") do
count.times { 'a'&.length }
end
end
Here are the results on my machine:
user system total real
nil.try!(:length) 1.700000 0.080000 1.780000 ( 1.786330)
'a'.try!(:length) 3.170000 0.130000 3.300000 ( 3.312468)
nil&.length 0.380000 0.000000 0.380000 ( 0.380821)
'a'&.length 0.820000 0.050000 0.870000 ( 0.872184)
Wow, &.
is 4x faster than try!
! Upon learning this, I went to regex hell and back and wrote a one line shell script to automatically convert usages of try!
to &.
in all Ruby/HAML files in the current directory and subdirectories:
find . \
-type f \
\( -name '*.rb' -o -name '*.haml' \) \
-exec sed -i 's/\.try!(:\([^,)]\+\))/\&.\1/g' '{}' \; \
-exec sed -i 's/\.try!(:\([^,)]\+\)\, *\([^)]\+\)\?)/\&.\1(\2)/g' '{}' \;
Note: this assumes GNU sed, so if you’re on MacOS you’ll need to brew install gnu-sed
and replace sed
calls with gsed
.
Hopefully this one-liner will help you migrate too!