Skip to main content
Engineering LibreTexts

13.4: Returning from Inside a Block

  • Page ID
    43739
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    In this section we explain why it is not a good idea to have return statements inside a block (such as [^ 33]) that you pass or store into instance variables. A block with an explicit return statement is called a non-local returning block. Let us start illustrating some basic points first.

    Basics on return

    By default the returned value of a method is the receiver of the message i.e., self. A return expression (the expression starting with the character ^) allows one to return a different value than the receiver of the message. In addition, the execution of a return statement exits the currently executed method and returns to its caller. This ignores the expressions following the return statement.

    Experiment 7: Return's Exiting Behavior. Define the following method. Executing Bexp new testExplicitReturn prints ’one’ and ’two’ but it will not print not printed, since the method testExplicitReturn will have returned before.

    Bexp>>testExplicitReturn
        self traceCr: 'one'.
        0 isZero ifTrue: [ self traceCr: 'two'. ^ self].
        self traceCr: 'not printed'
    

    Note that the return expression should be the last statement of a block body.

    Escaping behavior of non-local return

    A return expression behaves also like an escaping mechanism since the execution flow will directly jump out to the current invoking method. Let us define a new method jumpingOut as follows to illustrate this behavior.

    Bexp>>jumpingOut
        #(1 2 3 4) do: [:each |
                    self traceCr: each printString.
                    each = 3
                        ifTrue: [^ 3]].
        ^ 42
    
    Bexp new jumpingOut
        → 3
    

    For example, the following expression Bexp new jumpingOut will return 3 and not 42. ^ 42 will never be reached. The expression [ ^3 ] could be deeply nested, its execution jumps out all the levels and return to the method caller. Some old code (predating introduction of exceptions) passes non-local returning blocks around leading to complex flows and difficult to maintain code. We strongly suggest not using this style because it leads to complex code and bugs. In subsequent sections we will carefully look at where a return is actually returning.

    Understanding return

    Now to see that a return is really escaping the current execution, let us build a slightly more complex call flow. We define four methods among which one (defineBlock) creates an escaping block, one (arg:) evaluates this block and one (evaluatingBlock:) that executes the block. Note that to stress the escaping behavior of a return we defined evaluatingBlock: so that it endlessly loops after evaluating its argument.

    Bexp>>start
        | res |
        self traceCr: 'start start'.
        res := self defineBlock.
        self traceCr: 'start end'.
        ^ res
    
    Bexp>>defineBlock
        | res |
        self traceCr: 'defineBlock start'.
        res := self arg: [ self traceCr: 'block start'.
                            1 isZero ifFalse: [ ^ 33 ].
                            self traceCr: 'block end'. ].
        self traceCr: 'defineBlock end'.
        ^ res
    
    Bexp>>arg: aBlock
        | res |
        self traceCr: 'arg start'.
        res := self evaluateBlock: aBlock.
        self traceCr: 'arg end'.
        ^ res
    
    Bexp>>evaluateBlock: aBlock
        | res |
        self traceCr: 'evaluateBlock start'.
        res := self evaluateBlock: aBlock value.
        self traceCr: 'evaluateBlock loops so should never print that one'.
        ^ res
    

    Executing Bexp new start prints the following (indentation added to stress the calling flow).

    start start
        defineBlock start
            arg start
                evaluateBlock start
                    block start
    start end 
    

    What we see is that the calling method start is fully executed. The method defineBlock is not completely executed. Indeed, its escaping block [^33] is executed two calls away in the method evaluateBlock:. The evaluation of the block returns to the block home context sender (i.e., the context that invoked the method creating the block).

    When the return statement of the block is executed in the method evaluateBlock:, the execution discards the pending computation and returns to the method execution point that created the home context of the block. The block is defined in the method defineBlock. The home context of the block is the activation context that represents the definition of the method defineBlock.

    Therefore the return expression returns to the start method execution just after the defineBlock execution. This is why the pending executions of arg: and evaluateBlock: are discarded and why we see the execution of the method start end.

    Non-local return execution.
    Figure \(\PageIndex{1}\): A block with non-local return execution returns to the method execution that activated the block home context. Frames represent contexts and dashed frames represent the same block at different execution points.

    As shown by Figure \(\PageIndex{1}\), [^33] will return to the sender of its home context. [^33] home context is the context that represents the execution of the method defineBlock, therefore it will return its result to the method start.

    • Step 1 represents the execution up to the invocation of the method defineBlock. The trace 'start start' is printed.
    • Step 3 represents the execution up to the block creation, which is done in Step 2. 'defineBlock start' is printed. The home context of the block is the defineBlock method execution context.
    • Step 4 represents the execution up to the invocation of the method evaluateBlock:. 'arg start' is printed.
    • Step 5 represents the execution up to the block evaluation. 'evaluateBlock start' is printed.
    • Step 6 represents the execution of the block up to the condition: 'block start' is printed.
    • Step 7 represents the execution up to the return statement.
    • Step 8 represents the execution of the return statement. It returns to the sender of the block home context, i.e., just after the invocation of the method defineBlock in the method start. The execution continues and 'start end' gets printed.

    Non local return [^ ...] returns to the sender of the block home context, i.e., to the method execution point that called the one that created the block.

    A return in a method returns a value to the sender of the method and stop executing the method containing the return. A non-local return does the same even if the block is executed by another method.

    Accessing information. To manually verify and find the home context of a block we can do the following: Add the expression thisContext home inspect in the block of the method defineBlock. We can also add the expression thisContext closure home inspect which accesses the closure via the current execution context and gets its home context. Note that in both cases, even if the block is evaluated during the execution of the method evaluateBlock:, the home context of the block is the method defineBlock.

    Note that such expressions will be executed during the block evaluation.

    Bexp>>defineBlock
        | res |
        self traceCr: 'defineBlock start'.
        res := self arg: [ thisContext home inspect.
                            self traceCr: 'block start'.
                            1 isZero ifFalse: [ ^ 33 ].
                            self traceCr: 'block end'. ].
        self traceCr: 'defineBlock end'.
        ^ res
    

    To verify where the execution will end, you can use the expression thisContext home sender copy inspect. which returns a method context pointing to the assignment in the method start.

    Couple more examples. The following examples show that escaping blocks jump to sender of their home contexts. The previous example shows that the method start was fully executed. We define valuePassingEscapingBlock on the class BlockClosure as follows.

    BlockClosure>>valuePassingEscapingBlock
        self value: [ ^nil ]
    

    Then we define a simple assert: method that raises an error if its argument is false.

    Bexp>>assert: aBoolean
        aBoolean ifFalse: [Error signal]
    

    We define the following method.

    Bexp>>testValueWithExitBreak
        | val |
        [ :break |
            1 to: 10 do: [ :i |
                    val := i.
                    i = 4 ifTrue: [ break value ] ] ] valuePassingEscapingBlock.
        val traceCr.
        self assert: val = 4.
    

    This method defines a block whose argument break is evaluated as soon as the step 4 of a loop is reached. A variable val is then printed and we make sure that its value is 4. Executing Bexp new testValueWithExitBreak performs without raising an error and prints 4 to the Transcript: the loop has been stopped, the value has been printed, and the assert has been validated.

    If you change the valuePassingEscapingBlock message sent by value: [^ nil] in the testValueWithExitBreak method above, you will not get the trace because the execution of the method testValueWithExitBreak will exit when the block is evaluated. In this case, calling valuePassingEscapingBlock is not equivalent to calling value: [^nil] because the home context of the escaping block [ ^ nil ] is different. With the original valuePassingEscapingBlock, the home context of the block [^ nil] is valuePassingEscapingBlock and not the method testValueWithExitContinue itself. Therefore when evaluated, the escaping block will change the execution flow to the valuePassingEscapingBlock message in the method testValueWithExitBreak (similarly to the previous example where the flow came back just after the invocation of the defineBlock message). Put a self halt before the assert: to convince you. In one case, you will reach the halt while in another case not.

    Non-local return blocks. As a block is always evaluated in its home context, it is possible to attempt to return from a method execution which has already returned. This runtime error condition is trapped by the VM.

    Bexp>>returnBlock
        ^ [ ^ self ]
    Bexp new returnBlock value    → Exception
    

    When we execute returnBlock, the method returns the block to its caller (here the top level execution). When evaluating the block, because the method defining it has already terminated and because the block is containing a return expression that should normally return to the sender of the block home context, an error is signaled.

    Conclusion. Blocks with non-local expressions ([^ ...]) return to the sender of the block home context (the context representing the execution led to the block creation).


    This page titled 13.4: Returning from Inside a Block is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by Alexandre Bergel, Damien Cassou, Stéphane Ducasse, Jannik Laval (Square Bracket Associates) via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.