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.