Exceptions

From Lazarus wiki
Revision as of 00:56, 1 August 2018 by Kirinn (talk | contribs) (Fix try-link)

Free Pascal supports exceptions. Exceptions are useful for error handling and avoiding resource leaks. However, bear in mind that exceptions have a performance impact.

The official documentation is here: [1].

By default exceptions are disabled. You can opt in by using the OBJFPC or DELPHI Compiler Mode, or adding -Sx to the compiler commandline. This enables the try, raise, except, and finally keywords for use in your own code, but it doesn't enable exception generation from the RTL. To enable the RTL to raise exceptions instead of generating runtime errors, use the SysUtils unit in your program.

The base Exception class in SysUtils: [2]. All exceptions in code should preferably use this class or its descendants.


Examples

Note that Pascal uses different keywords than some other languages: raise instead of throw, and except instead of catch. Also, as a compiler design choice, each try-block can pair with either an except or finally block, but not both at the same time. You'll need to nest try-blocks if you need except and finally protecting the same code.

Error handling

try
  // Do something that might go wrong.
except
  on E : Exception do begin
    // Try to recover or show the user an error message.
  end;
end;

Cleaning up resources

try
  // Do something that might go wrong.
finally
  // Clean up code that is always called even if an exception was raised.
end;

Signalling problems

raise Exception.Create('Helpful description of what went wrong');

Using specialised exception types to signal different problems

type EMyLittleException = Class(Exception);

begin
  try
    raise EMyLittleException.Create('Foo');
  except
    on E : EMyLittleException do writeln(E.Message);
    on E : Exception do writeln('This is not my exception!');
  end;
end;


Exception classes

SysUtils defines and raises many specific exception classes: [3]


Best practice

As exceptions impose a performance penalty, some general guidelines apply:

  • Raise exceptions to signal that an operation that should have succeeded could not be completed.
  • Do not use exceptions as part of expected control flow. Instead, add checks for common error conditions and return error values the old-fashioned way.
  • But do raise an exception if it's critical that the error is noticed; programmers may forget to check for returned error values.
  • Naming convention: Prefix exception class names with a capital E.
  • Re-raise exceptions in except-blocks using raise; if you were unable to recover from the problem; this preserves the original exception's callstack.
  • Be careful about using the catch-all base Exception class, since the underlying code or OS/filesystem might produce an unanticipated exception that slips through your exception handler, potentially corrupting the program state.
  • Keep exception handling away from code that needs to run as fast as possible.


Performance

Compiler optimisation levels don't make much difference. All exception blocks come with a small amount of wrapper code that can't be optimised away.

To get a better feel for what's going on, try writing a small test program and compiling it with the -a switch. This leaves behind a human-readable assembly file.

  • Try-statements insert some extra address calculations, a stack push and pop, some direct jumps, and a conditional jump.
  • Except-statements insert the above, plus a push and pop, more direct jumps, and another conditional jump.
  • Finally-statements add a pop and a conditional jump, to allow re-raising an exception.
  • Raise-statements create a string and pass control to FPC's exception raiser.

Furthermore, if you want a human-readable callstack to aid debugging (using SysUtils functions ExceptAddr, ExceptFrames, ExceptFrameCount, and BackTraceStrFunc), that involves time-consuming stack traversal and string manipulation.

With all that said, outside of heavy processing code, the convenience of exceptions usually outweighs their performance impact. Don't feel bad about using them if it makes your code better.


Further reading

try

Logging exceptions

Avoiding implicit try finally section