Is the ruby operator ||= intelligent?

I have a question regarding the ||= statement in ruby and this is of particular interest to me as I'm using it to write to memcache. What I'm wondering is, does ||= check the receiver first to see if it's set before calling that setter, or is it literally an alias to x = x || y

This wouldn't really matter in the case of a normal variable but using something like:

CACHE[:some_key] ||= "Some String"

could possibly do a memcache write which is more expensive than a simple variable set. I couldn't find anything about ||= in the ruby api oddly enough so I haven't been able to answer this myself.

Of course I know that:

CACHE[:some_key] = "Some String" if CACHE[:some_key].nil?

would achieve this, I'm just looking for the most terse syntax.

Answers 1

  • This is extremely easy to test:

    class MyCache
      def initialize
        @hash = {}
      end
    
      def []=(key, value)
        puts "Cache key '#{key}' written"
        @hash[key] = value
      end
    
      def [](key)
        puts "Cache key '#{key}' read"
        @hash[key]
      end
    end
    

    Now simply try the ||= syntax:

    cache = MyCache.new
    cache["my key"] ||= "my value"  # cache value was nil (unset)
    # Cache key 'my key' read
    # Cache key 'my key' written
    
    cache["my key"] ||= "my value"  # cache value is already set
    # Cache key 'my key' read
    

    So we can conclude that no assignment takes place if the cache key already exists.

    The following extract from the Rubyspec shows that this is by design and should not be dependent on the Ruby implementation:

    describe "Conditional operator assignment 'obj.meth op= expr'" do
      # ...
      it "may not assign at all, depending on the truthiness of lhs" do
        m = mock("object")
        m.should_receive(:foo).and_return(:truthy)
        m.should_not_receive(:foo=)
        m.foo ||= 42
    
        m.should_receive(:bar).and_return(false)
        m.should_not_receive(:bar=)
        m.bar &&= 42
      end
      # ...
    end
    

    In the same file, there is a similar spec for [] and []= that mandates identical behaviour.

    Although the Rubyspec is still a work in progress, it has become clear that the major Ruby implementation projects intend to comply with it.


Related Articles