Safe navigation operator (&.) for nil
Asked Answered
A

3

27

As Ruby 2.3 introduces the Safe navigation operator(&.), a.k.a lonely operator, the behavior on nil object seems odd.

nil.nil?    # => true
nil&.nil?   # => nil

Is that designed to behave like this way? Or some edge case that slipped away when adding the lonely operator?

Advisory answered 4/1, 2016 at 0:15 Comment(0)
I
35

foo&.bar is shorthand for foo && foo.bar, so what would you expect the result of the expression nil && nil.nil? to be?

Illeetvilaine answered 4/1, 2016 at 0:22 Comment(0)
L
8

This is because nil&.nil? is shorthand for nil && nil.nil?. This would evaluate to nil && true, which is then nil.

(nil && x).nil? always evaluates to true, for any value of x.

While the syntax has power, this specific case has some potential to become a 'gotcha' for a developer:

(stuff&.things).nil? => This produces true if stuff doesn't exist, or stuff.things returns nil.

vs. the below case:

stuff&.things&.nil? => This produces nil in every case except the case where stuff.things returns something other than nil, in which case it would return false.

Because of the difficulty in normal boolean logic of differentiating between false and nil, it is unlikely that this would make sense in normal logic.

Lam answered 4/1, 2016 at 0:49 Comment(0)
K
0

safe navigation operator: tells Ruby to only call the next method if the receiver isn’t nil. Otherwise, the expression returns nil.

class Roster attr_accessor :players end

class Player
  attr_accessor :name, :position
  
  def initialize(name, position)
    @name = name
    @position = position
  end

end

With these two objects, we can create a roster for a 2-on-2 women’s basketball tournament:

moore = Player.new("Maya Moore", "Forward")
taurasi = Player.new("Diana Taurasi", "Guard")
tourney_roster1 = Roster.new
tourney_roster1.players = [moore, taurasi]

If we want to know the forward for our 2-on-2 team, we might find the name this way:

if tourney_roster1.players.first.position == "Forward"
  puts "Forward: #{tourney_roster1.players.first.name}"
end

But what if our opposing roster isn’t set correctly?

tourney_roster2 = Roster.new
if tourney_roster2.players.first.position == "Forward"
  puts "Forward: #{tourney_roster1.players.first.name}"
end

tourney_roster2 hasn’t yet been set with any players. The preceding code will raise a NoMethodError because tourney_roster2.players returns nil. We can add conditional statements to avoid this, but it makes our if statement verbose and unclear:

if tourney_roster2.players &&
   tourney_roster2.players.first &&
   tourney_roster2.players.first.position == "Forward"

Instead, we can use the safe navigation operator to avoid the NoMethodError:

if tourney_roster2.players&.first&.position == "Forward"
  puts "Forward: #{tourney_roster1.players.first.name}"
end

Some legitimate use cases: The safe navigation operator comes in handy when working with multiple objects, as shown here, and when chaining methods together.

Kranz answered 11/4, 2022 at 11:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.