Working on a Ruby program I was looking to move some state data from instance variables to class variables, it dawned on me that while instance variables are auto-vivified (if you try to read them "without initializing" them, they are automatically initialized to nil
), class variables are not - and this looks very inconsistent to me (compared to most Ruby syntax which is very consistent).
Sample program:
class Test
def id
@id.to_i
end
def id=(i)
@id = i
end
def nextid
self.id = id + 1
end
end
t = Test.new
puts t.nextid #=> 1
puts t.nextid #=> 2
In which case, when calling Test::id
, if @id
was not initialized, Ruby will auto-vivify it to nil
(after which I to_i
it to get 0).
Now I decide that I want the running ID to be shared across Test
instance, so I rewrite it like this:
class Test
def id
@@id.to_i
end
def id=(i)
@@id = i
end
def nextid
self.id = id + 1
end
end
t = Test.new
puts t.nextid
puts t.nextid
Should work the same, I thought, but no:
NameError: uninitialized class variable @@id in Test
But this workaround works (!?) :
class Test
def id
(@@id ||= 0).to_i
end
def id=(i)
@@id = i
end
def nextid
self.id = id + 1
end
end
t = Test.new
puts t.nextid #=> 1
puts t.nextid #=> 2
(granted, after doing lazy init to 0
I can drop the to_i
, but I left it for consistency).
It looks like Ruby understands "lazy initialization" and treats it as the magic needed to not throw NameError
- even though ||=
is supposedly just syntactic sugar to x = x || val
(which BTW doesn't work for initing class variables, thanks for asking).
How come?