From: jean.boussier@... Date: 2020-06-12T13:57:34+00:00 Subject: [ruby-core:98763] [Ruby master Bug#14541] Class variables have broken semantics, let's fix them Issue #14541 has been updated by byroot (Jean Boussier). I understand that backward compatibility concerns makes it very very improbable that such change would be implemented, but just to share. I learned Ruby coming from Python, and I almost immediately got bit by the class variable semantic. Here's how they work in Python: ```python class A: foo = 1 bar = 1 class B(A): bar = 2 print(A.foo) # => 1 print(A.bar) # => 1 print(B.foo) # => 1 print(B.bar) # => 2 ``` In short, they are inherited, but a subclass can only redefine a parent variable in its own scope, not rewrite it inside the parent scope. Just like methods do in Ruby. That's exactly the semantic Rails/ActiveSupport implements with [class_attribute](https://api.rubyonrails.org/classes/Class.html#method-i-class_attribute), and IMHO it makes so much more sense that the "true" class variables behavior. Unfortunately since Rails piggy back on the method semantic to implement this (setting a variable define a method with the value in a closure) they are way slower than they could be if they were natively supported. So I kind of wonder how much code would be actually broken by such change, or alternatively if there was a way to add that new behavior as a distinct syntax (`@$` or `$@` perhaps? It seems ugly but I have no other ideas). ---------------------------------------- Bug #14541: Class variables have broken semantics, let's fix them https://bugs.ruby-lang.org/issues/14541#change-86121 * Author: Eregon (Benoit Daloze) * Status: Assigned * Priority: Normal * Assignee: matz (Yukihiro Matsumoto) * ruby -v: ruby 2.6.0dev (2018-01-29 trunk 62091) [x86_64-linux] * Backport: 2.3: UNKNOWN, 2.4: UNKNOWN, 2.5: UNKNOWN ---------------------------------------- Class variables have the weird semantics of being tied to the class hierarchy and being inherited between classes. I think this is counter-intuitive, dangerous and basically nobody expects this behavior. To illustrate that, we can break the tmpdir stdlib by defining a top-level class variable: $ ruby -rtmpdir -e '$SAFE=1; @@systmpdir=42; p Dir.mktmpdir {}' -e:1: warning: class variable access from toplevel Traceback (most recent call last): 3: from -e:1:in `
' 2: from /home/eregon/prefix/ruby-trunk/lib/ruby/2.6.0/tmpdir.rb:86:in `mktmpdir' 1: from /home/eregon/prefix/ruby-trunk/lib/ruby/2.6.0/tmpdir.rb:125:in `create' /home/eregon/prefix/ruby-trunk/lib/ruby/2.6.0/tmpdir.rb:125:in `join': no implicit conversion of Integer into String (TypeError) Or even simpler in RubyGems: $ ruby -e '@@all=42; p Gem.ruby_version' -e:1: warning: class variable access from toplevel Traceback (most recent call last): 3: from -e:1:in `
' 2: from /home/eregon/prefix/ruby-trunk/lib/ruby/2.6.0/rubygems.rb:984:in `ruby_version' 1: from /home/eregon/prefix/ruby-trunk/lib/ruby/2.6.0/rubygems/version.rb:199:in `new' /home/eregon/prefix/ruby-trunk/lib/ruby/2.6.0/rubygems/version.rb:199:in `[]': no implicit conversion of String into Integer (TypeError) So defining a class variable on Object removes class variables in all classes inheriting from Object. Maybe @@systmpdir is not so prone to conflict, but how about @@identifier, @@context, @@locales, @@sequence, @@all, etc which are class variables of the standard library? Moreover, class variables are extremely complex to implement correctly and very difficult to optimize due to the complex semantics. In fact, none of JRuby, TruffleRuby, Rubinius and MRuby implement the "setting a class var on Object removes class vars in subclasses". It seems all implementations but MRI print :foo twice here (instead of :foo :toplevel for MRI): ~~~ ruby class Foo @@cvar = :foo def self.read @@cvar end end p Foo.read @@cvar = :toplevel p Foo.read ~~~ Is there any library actually taking advantage that class variables are inherited between classes? I would guess not or very few. Therefore, I propose to give class variable intuitive semantics: no inheritance, they behave just like variables of that specific class, much like class-level instance variables (but separate for compatibility). Another option is to remove them completely, but that's likely too hard for compatibility. Thoughts? -- https://bugs.ruby-lang.org/ Unsubscribe: