Variable Scope in Ruby
Defining scope between Global, Class, Instance and Local variables in Ruby
If you’re learning Ruby, one of the first concepts you’ll likely encounter – after Objects and Classes – is the concept of variables and how they differ by “scope.”
For this post I’ll focus on the latter point and try to illustrate how scope is defined across the different kinds of variables available to the Ruby programmer.
Ruby Variables
Local variables. Expressed lower case e.g. var
, str
, fido
, pommes_frites
Instance variables. Can start with lower or upper case letters. Can’t miss with their prepended ‘@’ sign e.g. @user
, @bike
, @Selfie
Class variables. Same rules apply as with their instance cousins, only prettier (or uglier, depending on your pov) with the double ‘@’ signs in front! @@User
, @@parcheesi
, @@Brooklyn
Global variables. They’re like pawns in chess who make it to the back row: they can be whatever they want, whenever they want (so long as they begin with a ‘$’ sign). Upper case. Lower case. Periods. Colons. Eat your heart out. $9
, $!!
, $foo_bar
, $AmericasGotTalent
, $meow
Now that you see how to tell the variables apart, let’s play with some example code to begin distinguishing between each variable’s scope.
Ruby variables at the class level
Let’s create a class and create an example of each variable.
class Verryables
print local_var = "local var at class level"
p @instance = "instance var at class level"
p @@class_var = "class var at class level"
p $GlobalVar = "global var at class level"
end
Simple. Run the code and you get exactly what you’d expect.
#=> "local var at class level"
#=> "instance var at class level"
#=> "class var at class level"
#=> "global var at class level"
Ruby variables and class instance methods
Now let’s define an instance method to see what happens when we (i) try to print the above class level variables, and (ii) reassign the variables.
class Verryables
...
def instance_method
p local_var
p @instance
p @@class_var
p $GlobalVar
end
def second_instance_method
puts "second instance method"
p @instance
p @@class_var
p $GlobalVar
end
end
What do you think the result would be? If you’re new to Ruby, think for a minute about what you expect will happen when we run it…
Survey says:
#=> "undefined local variable"
#=> nil
#=> "class var at class level"
#=> "global var at class level"
#=> "second instance method"
#=> nil
#=> "class var at class level"
#=> "global var at class level"
Is that what you were expecting? Now let’s play around and try to see what happens if we reassign (or define, as the case may be) these variables.
Testing scope between class methods and instance methods
Here we’ll change the messages, print them, and then go back up to the class level and print out the original variables to demonstrate which variables changed and which stayed the same.
class Verryables
puts ">> class level definitions"
p local_var = "local var at class level"
p @instance = "instance var at class level"
p @@class_var = "class var at class level"
p $GlobalVar = "global var at class level"
def instance_method
puts ">> begin instance method"
# p local_var (commented out, else "undefined local var" blows up the program)
p @instance
p @@class_var
p $GlobalVar
puts ">> assigned and reassigned, as the case may be"
p local_var = "local var from instance_method"
p @instance = "instance var from instance_method"
p @@class_var = "class var from instance_method"
p $GlobalVar = "global var from instance_method"
end
def second_instance_method
puts ">> second instance method"
p @instance
p @@class_var
p $GlobalVar
end
def self.class_method
puts ">> begin class method"
# p local_var #=> undefined local variable
p @instance
p @@class_var
p $GlobalVar
end
end
The action here will be in the second set of print outs (note: I’ve added a puts messages at the beginning of each method to help us see what messages are being printed from which method).
Survey says:
class Verryables
puts ">> class level definitions"
p local_var = "local var at class level"
p @instance = "instance var at class level"
p @@class_var = "class var at class level"
p $GlobalVar = "global var at class level"
#=> ">> class level definitions"
#=> "local var at class level"
#=> "instance var at class level"
#=> "class var at class level"
#=> "global var at class level"
def instance_method
puts ">> begin instance method"
# p local_var (commented out, else "undefined local var" blows up the program)
p @instance
p @@class_var
p $GlobalVar
puts ">> assigned and reassigned, as the case may be"
p local_var = "local var from instance_method"
p @instance = "instance var from instance_method"
p @@class_var = "class var from instance_method"
p $GlobalVar = "global var from instance_method"
end
#=> ">> begin instance method"
#=> nil
#=> "class var at class level"
#=> "global var at class level"
#=> ">> assigned and reassigned, as the case may be"
#=> "local var from instance_method"
#=> "instance var from instance_method"
#=> "class var from instance_method"
#=> "global var from instance_method"
def second_instance_method
puts ">> second instance method"
# p local_var #=> undefined local variable
p @instance
p @@class_var
p $GlobalVar
end
#=> ">> second instance method"
#=> "instance var from instance_method"
#=> "class var from instance_method"
#=> "global var from instance_method"
def self.class_method
puts ">> begin class method"
p local_var
p @instance
p @@class_var
p $GlobalVar
end
#=> ">> begin class method"
#=> undefined local variable
#=> "instance var at class level"
#=> "class var from instance_method"
#=> "global var from instance_method"
end
Phew, that’s bordering on TL;DR but I hope at least it helps illustrate how each variable responds when changing scopes (in this case, we went from the Class level, down to an instance of the class, and then back up to the Class level).
Variables, further defined
Here’s the definition for each variable, and how the code above illustrates it.
Local variable (local_var
in the earlier example).
As the name implies, local variables have limited scope – within a method definition, for example. Local variables don’t survive the scope change between class definitions and their inner method definitions. As such, local variable names can be reused in different scopes.
Using our earlier example, we could use the name local_var
throughout the program, and so long as each usage was within a different scope, each local_var
will be treated as completely separate.
That’s why, despite having defined a local_var
at the class level, when we initially called it within our instance method, the result was “undefined local variable.” Then within the instance method, we defined a local_var
, but as the program moved to the class method we again received an “undefined” error.
Think of scope as apartments in a building, and your keys as local variables. The key that gets you into one apartment is going to work for another apartment.
Instance variable (@instance
in the earlier example)
Instance variables are only visible to the object to which they belong. An instance variable initialized in one method definition, inside a particular class, is the same as the instance variable of the same name referred to in other method definitions of the same class.
In our code above, we defined an instance variable @instance
at the class level, and you can see that when we intitially called it, the return was nil
. However when we defined @instance
within instance method, and called it within the second instance method, we demonstrated that @instance
is the same across both instance methods of the same class. We’ve established that the @instance
’s scope encompasses both the class and the instance method.
Then jumping back to the class level, via class_method, we can see the @instance
that got printed was the original defined at the top class level. Thus, instance variables share scope across instances of the class, but that instance variables do not survive the change in scope from class level to the class instance.
Class variable (@@class_var
in the earlier example)
Class variables’ scope is shared between a class and instances of that class, yet is not visible to any other objects. Typically, this means class variables are used to share data between class-method definitions and instance- method definitions.
To illustrate this, our example shows how definig our class variable @@class_var
at the class level, we are then successfully able to call it within the instance method.
Because @@class_var
’s scope is shared vertically, we can reassign its value anywhere, and the change in state will be maintained elsewhere in the program. Therefore, when we call @@class_var
again in the second instance method, the new message is printed, and finally when we call it again in the class method. In the final class method, @@class_var
didn’t change back to the original class level definition, like we saw with the instance variable @instance
.
Global variable ($GlobalVar
in the earlier example)
Within a class, global variables offer the same functionality as class variables. What distinguishes a global variable from a class variables is that global variables are visible all over the program, from within a class to instances of the class, to between classes and objects.
Class v. global variables
In the example above, since we’ve only defined one class, there’s no practical dinstinction between how the global variable behaved v. the class variable.
So let’s quickly create a new example, with two distinct classes, to demonstrate how global and class variables differ.
class Class1
$GlobeTrotter = "Global variable from class 1."
p $GlobeTrotter
@@ClassVariable = "Class variable from class 1."
p @@ClassVariable
end
class Class2
puts ">> calling Class 2"
p $GlobeTrotter
p @@ClassVariable
end
c1 = Class1.new
c2 = Class2.new
#=> "Global variable from class 1."
#=> "Class variable from class 1."
#=> ">> calling Class 2"
#=> "Global variable from class 1."
#=> uninitialized class variable @@ClassVariable in Class2 (NameError)
There you have it. We’ve explored bit how scope differs between the four kinds of variables in Ruby. Even though these concepts are quite basic, in my experience as a developer so far, I haven’t had many opportunities to use class and global variables (whereas local and instance variables are quite common, particularly in Rails apps), so it’s been a helpful exercise in getting it all properly sorted in my head :)
Would love your feedback, and I’d welcome any thoughts or clarifications in the comments below. Would love your feedback, and I’d welcome any thoughts or clarifications in the comments below. Jk. I don’t have comments enabled why don’t you tweet at me!