This is the second post of the block closure series. This blog post is extracted from “Deep Into Pharo” and a new book under writing that will revisit chosen pieces of Pharo. We will start to play with Pharo to illustrate block and their interplay with variables.
Variables and blocks
A block can have its own temporary variables. Such variables are initialized during each block execution and are local to the block. We will see later how such variables are kept. Now the question we want to make clear is what is happening when a block refers to other (non-local) variables. A block will close over the external variables it uses. It means that even if the block is executed later in an environment that does not lexically contain the variables used by a block, the block will still have access to the variables during its execution. Later, we will present how local variables are implemented and stored using contexts.
In Pharo, private variables (such as self, instance variables, method temporaries and arguments) are lexically scoped: an expression in a method can access to the variables visible from that method, but the same expression put in another method or class cannot access the same variables because they are not in the scope of the expression (i.e., visible from the expression).
At runtime, the variables that a block can access, are bound (get a value associated to them) in ”the context” in which the block that contains them is ”defined”, rather than the context in which the block is executed. It means that a block, when executed somewhere else can access variables that were in its scope (visible to the block) when the block was ”created”. Traditionally, the context in which a block is defined is named the ”block home context”.
The block home context represents a particular point of execution (since this is a program execution that created the block in the first place), therefore this notion of block home context is represented by an object that represents program execution: a context object in Pharo. In essence, a context (called stack frame or activation record in other languages) represents information about the current execution step such as the context from which the current one is executed, the next byte code to be executed, and temporary variable values. A context is a Pharo execution stack element. This is important and we will come back later to this concept.
A block is created inside a context (an object that represents a point in the execution).
Some little experiments
Let’s experiment a bit to understand how variables are bound in a block.
Define a class named BExp (for BlockExperiment) as a subclass of TestCase so that we can define quickly some tests to make sure that our results are always correct.
TestCase subclass: #BExp
instanceVariableNames: ”
classVariableNames: ”
package: ‘BlockExperiment’
Experiment 1: Variable lookup.
A variable is looked up in the block definition context.
We define two methods: one that defines a variable t and sets it to 42 and a block [ t ] and one that defines a new variable with the same name and executes a block defined elsewhere.
BExp >> setVariableAndDefineBlock
| t |
t := 42.
^ self executeBlock: [ t ]
BExp >> executeBlock: aBlock
| t |
t := 33.
^ aBlock value
BExp new setVariableAndDefineBlock
>>> 42
The following test passes. Executing the BExp new setVariableAndDefineBlock expression returns 42 .
BExp >> testSetVariableAndDefineBlock
self assert: self setVariableAndDefineBlock equals: 42
The value of the temporary variable t defined in the setVariableAndDefineBlock method is the one used rather than the one defined inside the method executeBlock: even if the block is executed during the execution of this method.
The variable t is looked up in the context of the block creation (context created during the execution of the method setVariableAndDefineBlock and not in the context of the block evaluation (method executeBlock: ).
Let’s look at it in detail. The following figure shows the execution of the expression BExp new setVariableAndDefineBlock .

- During the execution of method setVariableAndDefineBlock , a variable t is defined and it is assigned 42. Then a block is created and this block refers to the method activation context – which holds temporary variables.
- The method executeBlock: defines its own local variable t with the same name than the one in the block. This is not this variable, however, that is used when the block is executed. While executing the method executeBlock: the block is executed, during the execution of the expression t the non-local variable t is looked up in the home context of the block i.e., the method context that created the block and not the context of the currently executed method.
Non-local variables are looked up in the home context of the block (i.e., the method context that created the block) and not the context executing the block.
Experiment 2: Changing a variable value
Let’s continue our experiments. The method setVariableAndChangingVariableBlock shows that a non-local variable value can be changed during the evaluation of a block.
Executing BExp new setVariableAndChangingVariableBlock returns 2008, since 2008 is the last value of the variable t .
BExp >> setVariableAndChangingVariableBlock
| t |
t := 42.
^ self executeBlock: [ t := 2008. t ]
The test verifies this behavior.
BExp >> testSetVariableAndChangingVariableBlock
self assert: self setVariableAndChangingVariableBlock equals: 2008
Experiment 3: Accessing a shared non-local variable.
Different blocks can share a non-local variable and they can modify the value of this variable at different moments.
To see this, let us define a new method accessingSharedVariables as follows:
BExp >> accessingSharedVariables
| t |
^ String streamContents: [ :st |
t := 42.
self executeBlock: [ st print: t. st cr. t := 99. st print: t. st cr ].
self executeBlock: [ st print: t. st cr. t := 66. st print: t. st cr ].
self executeBlock: [ st print: t. st cr. ] ]
The following test shows the results:
BExp >> testAccessingSharedVariables
self assert: self accessingSharedVariables equals: ’42
99
99
66
66
‘
BExp new accessingSharedVariables will print 42, 99, 99, 66 and 66.
Here the two blocks [ st print: t. st cr. t := 99. st print: t. st cr ] and [ st print: t. st cr. t := 66. st print: t. st cr ] access the same variable t and can modify it. During the first execution of the method executeBlock: its current value 42 is printed, then the value is changed and printed. A similar situation occurs with the second call. This example shows that blocks share the location where variables are stored and also that a block does not copy the value of a captured variable. It just refers to the location of the variables and several blocks can refer to the same location.
Experiment 4: Variable lookup is done at execution time.
The following example shows that the value of the variable is looked up at runtime and not copied during the block creation. First add the instance variable block to the class BExp .
Object subclass: #BExp
instanceVariableNames: ‘block’
classVariableNames:
package: ‘BlockExperiment’
Here the initial value of the variable t is 42. The block is created and stored into the instance variable block but the value to t is changed to 69 before the block is executed. And this is the last value (69) that is effectively printed because it is looked up at execution-time in the home context of the block. Executing BExp new variableLookupIsDoneAtExecution return ’69’ as confirmed by the tests.
BExp >> variableLookupIsDoneAtExecution
^ String streamContents: [ :st |
| t |
t := 42.
block := [ st print: t ].
t := 69.
self executeBlock: block ]
BExp >> testVariableLookupIsDoneAtExecution
self assert: self variableLookupIsDoneAtExecution equals: ’69’
Experiment 5: For method arguments
We can naturally expect that method arguments referred by a block are bound in the context of the defining method.
Let’s illustrate this point. Define the following methods.
BExp >> testArg5: arg
^ String streamContents: [ :st |
block := [ st << arg ].
self executeBlockAndIgnoreArgument: ‘zork’]
BExp >> executeBlockAndIgnoreArgument: arg
^ block value
BExp >> testArg5
self assert: (self testArg5: ‘foo’) equals: ‘foo’
Executing BExp new testArg: ‘foo’ returns ‘foo’ even if in the method executeBlockAndIgnoreArgument: the temporary arg is redefined.
The block execution looks for the value of arg is its definition context which is the one where testArg5: ‘ arg is bound to ‘foo’ due to the execution of method testArg5 .
Experiment 6: self binding
Now we may wonder if self is also captured in a block. It should be but let us test it.
To test we need another class.
Let’s simply define a new class and a couple of methods.
Add the instance variable x to the class BExp and define the initialize method as follows:
Object subclass: #BExp
instanceVariableNames: ‘block x’
classVariableNames:
package: ‘BlockExperiment’
BExp >> initialize
x := 123.
Define another class named BExp2 .
Object subclass: #BExp2
instanceVariableNames: ‘x’
classVariableNames: ”
package: ‘BlockExperiment’
BExp2 >> initialize
x := 69.
BExp2 >> executeBlockInAnotherInstance6: aBlock
^ aBlock value
Then define the methods that will invoke methods defined in BExp2 .
BExp >> selfIsCapturedToo
^ String streamContents: [ :st |
self executeBlockInAnotherInstance6: [ st print: self ; print: x ] ]
BExp >> executeBlockInAnotherInstance6: aBlock
^ NBexp2 new executeBlockInAnotherInstance6: aBlock
Finally we write a test showing that this is indeed the instance of BExp that is bound to self even if it is executed in BExp2.
BExp >> testSelfIsCapturedToo
self assert: self selfIsCapturedToo equals: ‘NBexp>>#testSelfIsCapturedToo123’
Now when we execute BExp new selfIsCapturedToo , we get NBexp>>#testSelfIsCapturedToo printed showing that a block captures self too, since an instance of BExp2 executed the block but the printed object ( self ) is the original BExp instance that was accessible at the block creation time. The printing is NBexp>>#testSelfIsCapturedToo because BExp is subclass of TestCase .
Conclusion
We show that blocks capture variables that are reached from the context in which the block was defined and not where there are executed. Blocks keep references to variable locations that can be shared between multiple blocks. In subsequent posts we will show how this is optimized by the Pharo compiler and VM to avoid expensive lookups.