class A
@@bla = [1, 2, 3]
def self.bla
@@bla
end
end
class B < A
end
p A.bla
p B.bla
A.bla << 4
p A.bla
p B.bla
crystal 1.cr
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
ruby 1.cr
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4]
This behavior is documented here https://crystal-lang.org/reference/syntax_and_semantics/class_variables.html
Class variables are inherited by subclasses with this meaning: their type is the same, but each class has a different runtime value.
I mean the behavior of class variables is coherent with behavior of instance variables in Crystal:
class A
@bla = [1, 2, 3]
def bla
@bla
end
end
class B < A
end
a = A.new
b = B.new
p a.bla
p b.bla
a.bla << 4
p a.bla
p b.bla
crystal 1.cr
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
Yes this is intentionally different from Ruby, whose behavior is the more surprising one generally and a common pitfall there.
Having different class variables for inherited classes have no sense. I think possibility of share @@ variables was the main goal why they was invented in ruby.
Having different class variables for inherited classes have no sense.
Yet having different instance variables for "inherited instances" has perfect sense?
Why?
Official Ruby FAQ warns about possible problems when using class variables in Ruby:
https://www.ruby-lang.org/en/documentation/faq/8/
What is the difference between class variables and class instance variables?
The main difference is the behavior concerning inheritance: class variables are shared between a class and all its subclasses, while class instance variables only belong to one specific class.
Class variables in some way can be seen as global variables within the context of an inheritance hierarchy, with all the problems that come with global variables. For instance, a class variable might (accidentally) be reassigned by any of its subclasses, affecting all other classes:
class Woof
@@sound = "woof"
def self.sound
@@sound
end
end
Woof.sound # => "woof"
class LoudWoof < Woof
@@sound = "WOOF"
end
LoudWoof.sound # => "WOOF"
Woof.sound # => "WOOF" (!)
Or, an ancestor class might later be reopened and changed, with possibly surprising effects:
class Foo
@@var = "foo"
def self.var
@@var
end
end
Foo.var # => "foo" (as expected)
class Object
@@var = "object"
end
Foo.var # => "object" (!)
So, unless you exactly know what you are doing and explicitly need this kind of behavior, you better should use class instance variables.
@asterite Sorry to ping you, but was the following behavior also intentional?
UPDATE:
My statement below is not true. See equivalent Crystal code by @straight-shoota below https://github.com/crystal-lang/crystal/issues/8427#issuecomment-549178421
On the interesting side note class variables in Crystal ARE NOT semantically equivalent to class instance variables in Ruby either. E.g. each class instance does not seem to have a separate variable like in this Ruby example from Official Ruby FAQ:
class Entity
@instances = 0
class << self
attr_accessor :instances # provide class methods for reading/writing
end
def initialize
self.class.instances += 1
@number = self.class.instances
end
def who_am_i
"I'm #{@number} of #{self.class.instances}"
end
def self.total
@instances
end
end
entities = Array.new(9) { Entity.new }
p entities[6].who_am_i # => "I'm 7 of 9"
p Entity.instances # => 9
p Entity.total # => 9
In Crytsal the equivalent code would print this instead
p entities[6].who_am_i # => "I'm 9 of 9" # NOTICE 9, not 7
p Entity.instances # => 9
p Entity.total # => 9
There are no eigenclasses in Crystal. I think thats related but I'm not sure.
@vlazar What do you consider equivalent code in Crystal?
I've got this and it has the same output:
class Entity
class_property instances = 0
@number : Int32
def initialize
self.class.instances += 1
@number = self.class.instances
end
def who_am_i
"I'm #{@number} of #{self.class.instances}"
end
def self.total
@@instances
end
end
entities = Array.new(9) { Entity.new }
p! entities[6].who_am_i # => "I'm 7 of 9"
p! Entity.instances # => 9
p! Entity.total # => 9
@straight-shoota Thank you for checking this, you are right! I've messed things up apparently.
It's good Crystal has only 1 type of class variables. Less room for confusion.
Most helpful comment
Yes this is intentionally different from Ruby, whose behavior is the more surprising one generally and a common pitfall there.