Variable Scope and Access in Ruby: The Important Parts

What is Variable Scope? How about Name Resolution?

Scope refers to visibility: what things are visible where in your program. In practice, the term “scope” tends to be used in two different contexts:

  1. What is “in scope” at a particular point in a program? What are the visible variables, methods, and classes currently accessible?
  1. When the resolution occurs

Scope and Name Resolution: A Visualization

Before diving any deeper, let’s walk through a visualization to build a clearer picture of the two interpretations of scope, along with name resolution.

  • Receiver or “calling object” (the value of self)
Photo by Marc Sendra Martorell on Unsplash

Why Does Scope Matter?

To understand why scope matters, consider a program’s experience without scope or resolution rules: everything would be accessible everywhere. Programs and their variables would operate in one giant bubble. This might not be a problem for minuscule scripts, but for anything of substantial size, this has tremendous implications on:

  1. Data Protection and Security: Scope restricts accidental or intentional access of variables in different parts of a program.
  2. Object-Oriented Programming: Scope is foundational to OOP’s encapsulating wonders. Without scope boundaries, encapsulation would be impossible. In fact, OOP introduces variable types (instance and class variables) with specific scoping rules designed to encapsulate state. For those in RB120: if the primary mechanism for encapsulating methods is method access control, we can think of scope as variable access control.

How does Ruby Set a Variable’s Scope?

We know that variables have scopes, but how do variables get their scopes in the first place? I’ve come across different semantics surrounding Ruby’s scoping behaviors. Regardless of specific terminology, I have deduced three ways that the scope of a variable is determined:

  1. The execution context (binding) of the program when the variable is initialized: you (the program) can add new variables into the bubbles you currently reside in. Local, class and instance variables use this criterion to determine their scope.
  2. The mere structure of the code surrounding the variable where it is initialized. This sounds similar to the previous point but is subtly different. This type of scope depends solely on location. The program doesn’t even need to run to evaluate variables’ scope like this; it can be figured out beforehand. A variable scoped in this way is said to have pure lexical scope. Constants use this criterion to determine their scope.

Scoping and Resolution: From the Variable’s Perspective

Now we will take a deeper dive into the particular rules governing each variable type’s scope, and how those variables are accessed when they’re needed.

Local Variables

  • Scope: The class/module definition, method, or block in which it is initialized; local variables have the most narrow scope out of all variable types.
  • Resolution: Given a name with all lowercase characters, the current binding is searched for both local variables and methods (think binding.local_variables).

Instance Variables

  • Scope: In Ruby, all code executes in the context of some calling object, otherwise known as the “receiver”. Every method call has some receiver: the receiver of an instance method is either explicitly defined: receiver.method; otherwise, it is implied — the receiver of method without anything prepended is self. The point is that when any method is invoked, a specific calling object is executing. If an instance variable is initialized under an object’s execution, it is scoped to that object.
  • Resolution: A name prepended with @ signals Ruby to search for an instance variable in the scope of the currently executing object: which can be thought of as self or binding.receiver.

Class Variables

  • Scope: Class variables can be thought of as global variables in the context of classes. A class variable initialized anywhere in a class definition has a scope that includes the class in which it’s defined, any instances of that class, any subclasses of that class, and any instances of those subclasses — a pretty wide scope indeed.
  • Resolution: A name prepended with @@ will search for a class variable in class-level scope. Because of their broad scoping rules, only one shared copy of a class variable with some name can exist among a class and all its subclasses. Therefore, if @@class_variable is initialized in a superclass, references to it in any subclasses will resolve to the same variable.

Constants (Constant Variables)

Constants are where things get fun. They might seem unimportant in comparison to other variable types until you learn that module and class names are themselves constants — and module and class names are referenced all the time. Since constants are intended to be static entities that don’t change during runtime, Ruby assigns them some special scoping and resolution rules.

  • Resolution: Unlike other variables, constants are resolved before runtime, before any notion of an “execution context”. This might seem strange, but Ruby achieves this with a step-by-step procedure after scanning for constant references (any name beginning with an uppercase letter). For each reference to a constant that’s needed at runtime, the following sequence of steps takes place:
  1. The inheritance hierarchy of the innermost currently open class/module is searched.
  2. The top-level is searched (any constants defined outside of a class/method definition, at the “top-level”).

Global Variables

I won’t discuss global variables in detail here because using them is widely considered malpractice, and their scoping rules are pretty simple: the scope of a global variable is the entire program.

Scoping and Name Resolution: From the Program’s Perspective

Going back to our two interpretations of the term “scope”, we will now take a moment to look at the second interpretation. Since the two interpretations are inverses (in a way), this section will provide an alternate perspective and serve as a mental reinforcement of the scoping rules defined in the last section. We will be focusing on answering the following question: how do variables become in scope during program execution?

  1. The currently executing object (the “calling object”): Value of self at a given point

Scope Gates

Ruby has a set of keywords that serve as [local variable] scope boundaries: module, class, def, and end. The first three signal Ruby to exit the current scope and enter a new “inner” scope, while end tells Ruby to exit the “inner” scope and enter the immediate “outer” scope. These keywords are known as scope gates, and understanding them is quite simple. Picture some bubbles as having strictly defined surfaces: these surfaces are scope gates.

The Calling Object

Secondly, the currently executing object, self, strongly relates to the accessible instance and class variables. To provide a rough approximation, all instance variables defined on the current self are accessible, and all class variables defined on self or the class/superclasses of self are accessible.

In Conclusion: Scope is Complicated

That dive may have felt a bit too deep for our purposes here at Launch School. And if we wanted to, we could dive 300 meters deeper. But as a programmer, this mental model, combined with your own ideas, will help you gain more confidence and clarity on how scoping rules arise from what may have seemed like nothingness. You now see how scope relates to its counterpart, name resolution. You might not fully understand these mental models and rules after one, two, or [insert finite number here] reads, so use this article as a reference for when you need it.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ethan Weiner

Ethan Weiner

Aspiring software engineer, learning at Launch School