Partager via


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

  • Anonymous
    December 16, 2007
    I'm very curious. How does the IL look like that IronPython generates?

  • Anonymous
    December 16, 2007
    Python: return self.i++ class Sample:    def meth(self):        try:            return self.i        finally:            self.i += 1

  • Anonymous
    December 16, 2007
    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.

  • Anonymous
    December 17, 2007
    The comment has been removed