Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scope of class variable

Setting a class variable @@foo in two classes B and C, where neither is a subclass of the other but they both include a common module A, seems to create @@foo separately for B and C, which A cannot access:

module A; end
class B; include A; @@foo = 1 end
class C; include A; @@foo = 2 end

module A; p @@foo end # => NameError: uninitialized class variable @@foo in A
class B; p @@foo end  # => 1
class C; p @@foo end  # => 2

But when @@foo is assigned in A, which works as an ancestor to both B and C, the @@foo that B and C access becomes the @@foo of A.

module A; @@foo = 3 end
class B; p @@foo end  # => 3
class C; p @@foo end  # => 3

What happened to the @@foo of B and C? Are they deleted when any of its ancestor's @@foo is assigned?

like image 235
sawa Avatar asked Oct 20 '22 14:10

sawa


1 Answers

This code appears in both rb_cvar_set and rb_cvar_get in MRI's variable.c:

if (front && target != front) {
    st_data_t did = id;

    if (RTEST(ruby_verbose)) {
        rb_warning("class variable %"PRIsVALUE" of %"PRIsVALUE" is overtaken by %"PRIsVALUE"",
          QUOTE_ID(id), rb_class_name(original_module(front)),
          rb_class_name(original_module(target)));
    }
    if (BUILTIN_TYPE(front) == T_CLASS) {
        st_delete(RCLASS_IV_TBL(front),&did,0);
    }
}

id is the C-internal representation of the variable name (@@foo).

front is the class in which the variable is currently being accessed (B/C).

target is the most distant ancestor in which the variable has also ever been defined (A).

If front and target are not the same, Ruby warns that class variable #{id} of #{front} is overtaken by #{target}.

The variable name is then literally deleted from front's RCLASS_IV_TBL, so that on subsequent lookups, the search for that variable name "falls through" or "bubbles up" to the most distant ancestor in which the variable is defined.


Note that this check and deletion happen not just on cvar gets, but on sets as well:

$VERBOSE = true

module A; end
class B; include A; @@foo = 1; end # => 1

module A; @@foo = 3 end # => 3
class B; p @@foo = 1 end # => 1
#=> warning: class variable @@foo of B is overtaken by A


module A; p @@foo end # => 1

In this example, even though it's A's value of 3 being overwritten by the value 1 being set in B, we still receive the same warning that it's B's class variable being overtaken by A!

While it is usually more surprising to the average Ruby coder to find that the value of their variable is changing in various, perhaps unexpected, places (i.e. in "parent"/"grandparent"/"uncle"/"cousin"/"sister" modules and classes), the trigger and the wording both indicate that the warning is actually intended to inform the coder that the variable's "source of truth" has changed.

like image 138
user513951 Avatar answered Nov 03 '22 21:11

user513951