Local variable scopes in Ruby

3 takeaways about local variable scopes

  1. Scopes determine the accessibility of local variables. Therefeore, they determine the accessibility of the objects referenced by local variables.
  2. The scope in which a local variable is initialized matters.
  3. Scopes are delimited by specific keywords: the def..end keywords that define methods and the do..end keywords following a method invocation that define blocks.

7 rules of local variable scoping

  1. Inner scope local variables can access local variables initialized in an outer scope.
  2. Outer scope local variables cannot access local variables initialized in an inner scope.
  3. Peer scopes do not conflict.
  4. Nested blocks create nested scopes.
  5. Variable shadowing. If an inner scope block parameter and an outer scope local variable have the same name, the inner scope block parameter cannot access the outer scope local variable.
  6. A method definition has a scope that is entirely self-contained. It cannot access local variables initialized in other scopes.
  7. A method definition can access objects passed in as arguments.

Very precise code explanations for each rule

1. Inner scope local variables can access local variables initialized in an outer scope

On line 1, the local variable greeting is initialized and assigned to the String object 'Hello'.

On lines 3 to 6, the do..end keywords following the method invocation of Kernel#loop define a block and create an inner scope.

On line 4, within the inner scope, the local variable greeting is reassigned to the String object 'Hi'. This is possible because the inner scope local variable on line 4 can access the outer scope local variable initialized on line 1.

On line 8, the Kernel#puts method is called and the local variable greeting is passed to it as an argument.

Line 8 outputs the String object 'Hi', since the local variable greeting is now assigned to 'Hi'.

2. Outer scope local variables cannot access local variables initialized in an inner scope

On lines 1 to 4, the do..end keywords following the method invocation of Kernel#loop define a block and create an inner scope.

On line 2, the local variable b is initialized within the innner scope and assigned to the Integer object 1.

On line 6, the method Kernel#puts is called and the local variable b is passed to it as an argument.

The execution of line 6 throws a NameError, because the outer scope local variable b on line 6 cannot access the local variable b initialized in the inner scope on line 2.

3. Peer scopes do not conflict

On lines 1 to 4, the do..end keywords following the method invocation of Integer#times on the Integer object 2 define a block and create an inner scope. On line 2, the local variable a is initialized and assigned to the String object 'hi'. On line 3, the method Kernel#puts is called. The local variable a, initialized within the inner scope on line 2, is passed to it as an argument. Therefore, the execution of lines 1 to 4 outputs 'hi' 2 times.

On lines 6 to 9, the do..end keywords following the method invocation of Kernel#loop define a block and create an inner scope. On line 7, the method Kernel#puts is called. A local variable a, which has not been initialized in the inner scope nor in the outer scope, is passed to the Kernel#put method as an argument. Also, the local variable a on line 7 cannot access the local variable a on line 2, as it is is in another scope. Therefore, the execution of lines 6 to 9 throws a NameError and the execution of the code stops on line 7.

4. Nested blocks create nested scopes

On lines 3 to 11, the do..end keywords following the method invocation of Kernel#loop define a block and create a first inner scope.

On line 6 to 12, the do..end keywords following the method invocation of Kernel#loop define a block and create a second inner scope.

On line 1, the local variable a is initialized in the outer scope and assigned to the Integer object 1. Therefore, on line 8, within the second inner scope, the local variable a can access the local variable initiliazed in the outer scope on line 1.

Similarly, on line 4, the local variable b is initialized in the first inner scope and assigned to the Integer object 2. Therefore, on line 9, within the second inner scope, the local variable b can access the local variable initiliazed in the first inner scope on line 4.

On line 7, the local variable c is initialized in the second inner scope and assigned to the Integer object 3. Therefore, on line 10, within the second inner scope, the local variable c can access the local variable initiliazed in the same scope on line 7. However, on line 14, within the first inner scope, the local variable c cannot access the local variable initiliazed in the second inner scope scope on line 7.

5. Variable shadowing

On line 1, the local variable a is initialized and assigned to the Integer object 4.

On line 3, the method Integer#times is called on the Integer object 2.

On lines 3 to 6, the do..end keywords following the method invocation of Integer#times define a block and create an inner scope.

On line 3, the |..| captures a block parameter named a. But, this is also the name of the outer scope local variable a, initialized on line 1. This causes variable shadowing. Therefore, the block parameter a cannot access the outer scope local variable a initialized on line 1. The execution of the code from line 4 to 8 outputs the Integer object 5 2 times.

On line 9, the execution of puts a outputs the Integer object 4. The outer scope local variable a initialized on line 1 was not re-assigned on line 5 because of variable shadowing.

6. A method definition has a scope that is entirely self-contained. It cannot access local variables initialized in other scopes

On line 1, the local variable a is initialized and assgined to the String object 'hi'.

On lines 3 to 5, the def..end keywords define a method named some_method, with no method parameters.

On line 4, the method Kernel#puts is called. A local variable a, which has not been initialized in the scope of the method definition, is passed in as an argument.

On line 7, the method some_method is called. This throws a NameError. The local variable a on line 4 cannot access the local variable a initialized in another scope.

7. A method definition can access objects passed in as arguments

On line 1 to 3, the def..end keywords define a method named some_method. This method has 1 method parameter named a.

On line 2, the method Kernel#puts is called and the method parameter a is passed to it as an argument.

On line 5, the local variable word is initialized and assigned to the String object 'Hi!'.

On line 7, the method some_method is called and the local variable word is passed to it as an argument.

Line 7 outputs 'Hi!', because the method some_method was able to access the object referenced by the local variable word that was passed in as an argument.

Initial publication date: 2019-08-07
Last updated: 2019-08-20