Return vs. Finally (2)


The Return statement and finally have competition. Both can are the “last” thing to execute when a function exits, so which of them is really last? I blogged about this before regarding C#’s semantics setting data. Now I’ll compare some interesting differences between C# and Python.

Who wins if you set them up in direct competition like this (non-exceptional path):

void f1()
    {
        try
        {
            return 10;
        }
        finally
        {
            return 5;
        }
    }

or this (the exceptional path):

    void f2()
    {
        try
        {
            throw new Exception();
        }
        finally
        {
            return 5;
        }
    }

 

There are multiple intelligent ways a language design could answer this.

C#’s answer: Illegal

In C#, this is just illegal. Returns are forbidden in finally clauses. This nicely just avoids the whole problem by refusing to let you write such potentially ambiguous and confusing code.  (CS0157: “Control cannot leave the body of a finally clause”). And if a consensus emerges down the road that there really is a clear “right” answer, C# can always make it legal with the “right” semantics.

IL’s answer: Illegal

The underlying CLR’s IL instruction provides try/catch/finally and branching opcodes. The only branching opcode out of a finally is ‘endfinally‘, which jumps to the end of the finally block. You can’t return / branch out of a finally. (See VER_E_RET_FROM_HND=0x80131845 in CorError.h).

Since .NET languages compile to IL, it may be natural for a language to have the same semantics as the IL instructions it compiles to.  It’s not surprising that C# has the same restrictions as the underlying IL  instruction set here.  However, a .NET language  can have more clever opcode usage to provide their own semantics.

 

Python’s answer: Legal

In Python, return inside of finally is actually allowed. 

>>> def f1():
...   try:
...     return 10
...   finally:
...     return 5
...
>>> f1()
5
>>> def f2():
...   try:
...     raise Exception # like 'throw'
...   finally:
...     return 5
...
>>>
>>> f2()
5

 

In this case, you can see that even on the exceptional path, the return statement will swallow the exception and return a value.

Note that IronPython compiles to IL, and still faithfully maintains the python exception semantics here.

Comments (4)

  1. .NET Junkie says:

    I’m very curious. How does the IL look like that IronPython generates?

  2. Niki says:

    Python: return self.i++

    class Sample:

       def meth(self):

           try:

               return self.i

           finally:

               self.i += 1

  3. StewartT says:

    If you treat the IL finally block as the moral equivalent of C++’s automatic destructor calls, then C++ has a hybrid of the two, where it is not possible to return from a "finally" block, since there is no where to put the return statement, however it is possible to throw. In C++ the consensus seems to be that throwing from destructors is a "bad thing"(TM), and I have only ever had cause to do it when writing classes expressly designed to do it as part of dynamic exception generation (don’t ask :)). It is particularly dodgy in C++ of course due to the amusing C++ double fault behaviour, where if you throw from a destructor during an exception unwind, std::terminate is called and the process is to all intents and purposes dead, however you cannot know at the time when you throw that this is going to happen.

    I’m not saying its right, but I thought it was another perspective i’d point out.

  4. Stewart – good point about C++, that would have been good for me to include at the top.

    Niki – yeah, that’s another twisted example. That’s similar to what I blogged about before, and C# has similar behavior.

    .NET Junkie: basically, you can build a finally block with try/catch blocks + rethrow.  It’s clunkier, and the stack-unwinding is worse under a debugger/watson, but it gives you much finer control.