Skip to content

Variables, Constants and Namespaces

datanorris edited this page Jan 10, 2016 · 16 revisions

Variables (including constants) in Ruby have the following key characteristics:

  • They are not "typed" - all variables can be assigned any Ruby object
  • They are not declared - they just pop into existence when they are first assigned to
  • The only characteristics of variables are their name, what they are attached to, and their value at any point in time - except for constants, which are marked either public or private
  • Constant accesses may be resolved through a const_missing method, similar to method_missing
  • The type of a variable (as in local/instance/class/global/constant) is determined by the first 1 or 2 characters in its name, so there are no name conflicts. However, constants and local variables can have the same name as methods.

In general, a variable name is any combination of alphanumeric, _, or non-ASCII characters.

Specific rules for the beginning of variable names:

  • Constants must begin with an uppercase ASCII character

  • Local variables must begin with a legal name character other than an ASCII uppercase or digit character

  • Instance variables must begin with @ followed by a legal name character other than an ASCII digit

  • Class variables must begin with @@ followed by a legal name character other than an ASCII digit

  • Global variables must have one of the following forms:

  • begin with $ followed by a legal name character other than an ASCII digit

  • be $ followed by an ASCII digit other than 0 followed by any number of ASCII digits (i.e. a regular expression match backreference)

  • be $- followed by a single legal name character

  • be one of the following forms:

         $~
         $*
         $$
         $?
         $!
         $@
         $/
         $\
         $;
         $,
         $.
         $=
         $:
         $<
         $>
         $"
         $&
         $`
         $'
         $+
         $0
    

Local variables

Local variables are, as with other languages, transient variables that are created when a unit of code runs and discarded afterwards (unless a closure is formed over them).

Local variables are pretty much the only thing in all of Ruby (apart from syntax rules and similar reasonable odds and ends) that is statically resolved at compile time. Failures to resolve cause compile-time errors.

Local variable gets are resolved by searching up the dynamic variable context stack, then the local variable context, for the first context where the local variable exists. If the local variable does not exist, and Ruby syntax permits, the reference is treated as a variable-call-style method call instead and resolved at runtime.

Local variable sets (assignments) are resolved by the same search - if not found, the local variable is created in the innermost dynamic variable context, or if not available, the local variable context.

Instance variables

Instance variables, as with other languages, are variables attached to an object and accessible from methods executing on that object. Instance variables can never be accessed directly "outside" of an object, although APIs do exist that allow you to access them.

Instance variable gets and sets are resolved to self. If an instance variable get is performed and the variable doesn't exist, it will return the value nil and Ruby will issue a warning.

Class variables

Class variables, as with other languages, are variables attached to a class (or module) and generally accessible from:

  • Code defining that class, in a class or module statement, including (if they were defined within such a statement)
  • Methods run on instances of the class
  • "Static methods" run on that class

Class variable gets, both direct and API, are resolved in the following odd way:

  • All possible resolutions of the class variable are identified with the following search:
  • For normal, direct class variable gets, begin searching with the class variable context class
  • For class variable gets on a specific class through API (i.e. the class_variable_get method), begin searching with that class
  • If the class is a metaclass (not possible with direct gets, as class variable context is never a singleton class), move to the class that the metaclass is attached to and search that
  • Move to each of the superclasses in turn and search them
  • The last (i.e. highest in the inheritance tree) class variable found is returned
  • If there was more than one resolution, the first class variable found is deleted from its class (because it has been overshadowed) with a warning
  • If no class variable was found, an exception is raised

Class variable sets are resolved in the exact same way, except that if no class variable was found, it will be created on the first class that was searched.

Global variables

Global variables, as with other languages, are global to a running Ruby program.

Global variable gets and sets are resolved straightforwardly. If a global variable set does not resolve, the global variable is created. If a global variable get does not resolve, the get will return nil.

It is worth knowing that internally, to the Ruby C API, global variables are not really variables - they are implemented as callbacks to arbitrary getter and setter functions which can be implemented however the C code sees fit. Internally, the following basic types of global variables can be created:

  • Value global variables: these function like regular variables. When Ruby code creates a global variable (as opposed to accessing one or setting an existing one), this is the type created.
  • Reference global variables: these are also like regular variables except that instead of the value being maintained in a location controlled by the global variable mechanism, it is controlled by some other component of C code. This means that the C code can alter the value of the global variable without going through the global variable API, and such alterations can't be traced (see tracing below)
  • Virtual global variables: these have arbitrary getter and setter functions, with the same tracing issue as reference global variables. A common example is where the setter function always raises an exception, thus making it a read-only global variable.

Once global variables have been created, they can't be removed except by redefining them as an alias of another global variable.

Global variable tracing

You can "trace" changes to global variables by using the trace_var method, providing callback code (a string, block or Proc) to be invoked whenever the global variable is changed. Multiple trace callbacks can be set, and they'll all be invoked when the global variable is changed, in the reverse of the order that they were set.

While a global variable's trace callbacks are in the process of being executed:

  • If the global variable is changed again, trace callbacks are not reinvoked
  • If any trace callbacks on the global variable are added or removed, this is deferred until all callbacks have been invoked in the current trace cycle

Global variable aliasing

Global variables can be aliased. Aliases of a single global variable share same structures, including the list of tracers.

While a global variable is being traced, you can't redefine it as an alias.

A small note on regular expression global variables

When direct gets in Ruby code are done on the global virtual variables $&amp;, &amp;`, $', $+ or a backreference (e.g. $1), Ruby actually does not parse these as global variable references, they are compiled directly into the operations that they represent (i.e. accessing the relevant value from the local variable context). This means that, while you can alias-redefine these global variables, in Ruby code gets the alias will not be honoured. Sets to these global variables will cause a compile-time error.

Constants

Constants have some differences to other languages. These are variables generally intended to be set once and then not changed, but this is not really enforced. They are attached to a class or module and, if public, accessible to any code that can access that class or module.

Referencing constants and their namespaces

Classes and modules themselves are objects usually stored in constants, which are stored in other classes and modules. Thus arises Ruby's one and only concept of a namespace hierarchy. A reference such as ::Ont::Person::FEMALE means the constant FEMALE in the Person class/module which is a constant inside the Ont class/module which as constant inside the Object class.

There are 3 ways a constant can be referred to:

  • MYCONST - an implicitly namespaced constant reference
  • <value>::MYCONST - an explicitly namespaced constant reference on the value provided. The value can be any Ruby expression but must resolve to a class or module.
  • Note that the :: operator used in this way is actually very similar to the . operator used to reference methods, and :: can be used to call methods as well. The only difference is that . can only reference methods, whereas :: will be interpreted as referencing a constant instead of a method if it's referencing an identifier that begins with an uppercase letter and is otherwise a valid constant name (but could still be a constant and/or a method), and it isn't obviously a method call (i.e. it isn't followed by arguments or a block)
  • ::MYCONST - an explicitly namespaced constant reference on the "root namespace" i.e. the Object class. Which Object class in what namespace? The "one true" Object class which is the common ancestor class of (almost) all other classes in Ruby.

As mentioned above, the root of the namespace hierarchy is effectively the Object class. This is a simplification because the rules for resolving constant references are a fairly complex, and Object itself has superclasses (e.g. the BasicObject class and the mixed-in Kernel module), however this simplification is generally effective because:

  • The ::MYCONST root namespace reference, which operates on the Object class
  • The most fundamental classes in Ruby, BasicObject, Object, Class and Module are all stored as constants on the Object class (although BasicObject is also stored as a constant on itself)
  • Almost all other core Ruby classes are stored as constants on the Object class. When we talk about Ruby classes just by name e.g. Array, we are generally talking about the Array class stored in Object (i.e. ::Array) unless explicitly stated otherwise.
  • Constant references in a top context (i.e. not nested inside a class or module statement) generally resolve to the Object class
  • The constant reference resolving algorithms will generally search on superclasses too, so if the Object class is searched its superclasses will be searched as well

Rules for modifying existing constants

Ruby makes some attempt to ensure that constants are really constant, and never altered. However, it doesn't really work, because the Ruby language does not have enough static declaration for proper strict constant management to be possible.

The following will produce compile time errors:

  • Assigning to an explicitly namespaced constant via a complex assignment operator (e.g. +=, or even ||=) because by definition this alters an existing constant
  • Assigning to a constant in code inside a def method definition statement (presumably because methods are expected to run multiple times and therefore constant assignments are likely to alter existing constants), unless:
  • That constant assignment is further nested inside a class statement defining a singleton class (presumably to support methods which create singleton classes on objects and assign constants to them)

Therefore you can still alter a constant:

  • with a complex assignment operator as long as the constant is implicitly namespaced (this could be considered a bug)
  • in a top, class statement or module statement context as long as you're not using a complex assignment operator
  • inside a def statement as long as you further nest it in a class statement, or perhaps use one of the eval APIs
  • with APIs that set constants

At runtime, altering a constant will cause Ruby to issue a warning.

Constant visibility

Similar to methods, if a constant is marked private, it can't be referenced by an explicitly namespaced constant reference - it must be implicit or through APIs.

All constants are created as public, but they can be set to private afterwards.

Autoloading

Autoloading is a feature whereby Ruby can be instructed that if something tries to get the value of a some particular constant which doesn't exist, on some particular class/module, Ruby is to load and run some particular file (either a Ruby source file or a Ruby C extension) in the expectation that this file will set the value of the constant.

When a constant resolving algorithm locates on some class/module an eligible constant which doesn't currently exist, but is marked for autoloading, the following will happen:

  • It will determine that the constant in fact doesn't exist on this class, and continue searching, if:
  • The associated file was already loaded successfully, according to C APIs associated with require and $LOADED_FEATURES (i.e. the file did not raise up an exception while loading) - if the file was loaded successfully, and the constant still doesn't exist, the autoload obviously didn't succeed in assigning the constant
  • The associated file is currently in the process of being loaded, and the code which has attempted to access the constant is in the same Ruby sourcefile as the one being loaded - so the file has a circular reference to the constant it is meant to be creating
  • Otherwise, it will attempt to load the file via the C API equivalent of the require method. Note that this means if the file is already in the process of being loaded (e.g. on another thread), it will wait for the file to complete loading before proceeding
  • It will then check again to see if the constant now exists. If it still doesn't exist (and is still therefore marked for autoloading), it will give up and continue searching for the constant elsewhere

Once an autoload flag has been set, it is only removed if the constant is assigned to, or the constant is removed (via API).

TODO - clarify this

The const_missing method

Similar to method_missing, constant gets, if they can't be resolved directly (including via autoloading), will invoke the const_missing method on the target class. const_missing is provided 1 argument, which is the constant's name in symbol form, and is expected to return the constant's value.

The default implementation of const_missing is to raise a NameError exception.

Note that for const_missing there is no analog to the respond_to? mechanisms associated with method_missing, which a class is expected to implement in order to declare whether it implements methods via method_missing. Also, const_missing has no bearing on constant sets.

Resolving explicitly-namespaced constants

Resolving implicitly-namespaced constants

Resolving the class/module statement constant

Querying whether a constant is defined

Note ev_const differs from const_defined here

"Search algorithm used for getting non-explicitly-namespaced constants in params:

  • iseq (seems to be cfp->iseq)
  • orig_klass (scope of constant, Qnil means current scope)
  • id
  • is_defined (1 if only checking definedness) if orig_klass provided, vm_check_if_namespace() and return rb_public_const_(defined|get)_from() search ""_of"" style on const-base-worthy classes in cref hierarchy, except for top cref (Object):
  • Loop up from vm_get_cref() searching for a non-null cref->nc_clss which is not PUSHED_BY_EVAL - except not looking in the top cref
  • Try getting the const by RCLASS_CONST_TBL()/st_lookup
  • If found but its Qundef, return 1 if is_defined, otherwise rb_autoload_load() and try again (if it's still Qundef afterwards, exit the loop)
  • If found and not Qundef, return 1 or val depending on is_defined Otherwise look for const (with full superclass search) in vm_get_cref()->nd_clss if present, otherwise CLASS_OF(cfp->self)
  • return rb_const_(defined|get)()"

"Main logic to get a const value. In params:

  • klass, id

  • exclude - if true rb_warn if klass is a class and const is on Object, and if klass is a module don't search on Object

  • recurse - walk up the inheritance tree

  • visibility - if true raise error if accessing a private constant If ecurse, walk up the inheritance tree from klass, otherwise just look at klass:

  • if class.consts[id] exists

    • if visibility flagged and ->flag == CONST_PRIVATE, raise error
    • if Qundef, rb_autoload_load() and retry. If on retry the value is still Qundef, exit loop
    • if exclude flagged and we are looking at rb_cObject, and klass != rb_cObject, rb_warn()
    • return value if !exclude and klass is a module, and we haven't tried this already, restart searching on rb_cObject (including superclasses) value = const_missing() rb_vm_inc_const_missing_count() return value"
  • 0, 3 (class Foo::Bar or class Bar)

    • if super is nil, make it Object
    • vm_check_if_namespace(cbase)
    • find the class: rb_autoload_load(cbase, id), klass = rb_(define_type == 0 ? public : "")_const_get_at(vm_search_const_defined_class(cbase, id), id)
    • if not a T_CLASS, raise exception
    • if super is not Object and not rb_class_real(RCLASS_SUPER(klass)) , raise exception
    • if klass not found, create with rb_define_class_id(), rb_set_class_path_string(), rb_const_set(cbase, id, klass), rb_class_inherited()

Clone this wiki locally