return statement doesn’t necessarily return to the call site that called the current method, but it can return at once from the callee, its caller, the caller’s caller, and so on.
A return statement can unwind multiple call stack frames
A return statement pops exactly one call stack frame
CorrectionHere is what's right.
return statement always returns to the call site that called the current method. It always unwinds only one call stack frame. It only finishes the current method activation, continuing execution right after the call site who is responsible for the current activation.
So, in a sequence diagram, this means a return arrow points to the same lifeline from which the corresponding call arrow came. It can’t point to a different lifeline.
The call of
Pedal.push() (1) is paired with return arrow (4), and the call of
Engine.accelerate() (2) is paired with return arrow (3).
SymptomsHow do you know your students might have this misconception?
A good way to detect this misconception is through the use of (UML-like) sequence diagrams. If the call and return arrows don’t match up, students might have this misconception. Here is an example incorrect sequence diagram:
While there are two nested calls (1 and 2), students may only draw a single return arrow (3) that unwinds both stack frames.
While this misconception applies to returns in general, it is particularly prevalent in returns from recursive calls. Students sometimes assume that all calls in a recursion can be completed at once by a single return statement. That is, they assume a return statement unwinds the call stack similar to the way an expression does.
ValueHow can you build on this misconception?
If students assume that
return unwinds multiple stack frames, then they may be happy to learn that
raise indeed may do so. Thus, when clarifying this misconception, one can explain that in exception handling indeed multiple stack frames can be unwound due to one single (
Continuation-Passing Style, Asynchronous Calls, Multithreading
The following ideas are somewhat less directly related to this misconception, but students may encounter them in more advanced courses. There are various reasons why one might draw variants of sequence diagrams where activations are not properly nested and properly delineated by call arrows and return arrows.
For example, in continuation-passing style (CPS), methods never get to return. A method always calls another method after it completes its own work. The last method to be called then kills the entire program. Thus no
return ever happens. This means that there won’t be any return arrows at all. (However, if the last method did not kill the program, then at that point the methods that had been called would return one by one.)
Another example where sequence diagrams somewhat deviate from the simple model are asynchronous calls (calls to asynchronous APIs), where a call does return, immediately, just to the caller, but the work on the callee may continue.
In general, in multi-threaded programs, there will be multiple stacks, each with its own proper nesting of calls and returns. However, a sequence diagram may include special arrows to denote communication between threads. And that might include arrows that are not paired with “return arrows”.