FPC Unleashed is a community-driven fork of Free Pascal, focused on pushing the language forward with modern, expressive, and practical features that have not (yet) been accepted into the official compiler.
Activate: {$mode unleashed} or -Munleashed
A modern Pascal mode based on objfpc with powerful enhancements enabled by default. Instead of toggling individual modeswitches, you get everything at once.
When using Lazarus Unleashed, this mode is enabled by default for all projects and full code completion is supported out of the box.
The following modeswitches are enabled automatically:
| Modeswitch | Description |
|---|---|
statementexpressions |
Use if, case, and try as expressions |
inlinevars |
Declare variables inline, anywhere inside a begin..end block |
anonymousfunctions |
Anonymous procedures and functions |
functionreferences |
Function pointers that capture context |
advancedrecords |
Records with methods, properties, and operators |
arrayoperators + arrayequality |
Direct array comparisons with = and <> |
ansistrings |
Uses AnsiString as the default string type |
underscoreisseparator |
Allows underscores in numeric literals (1_000_000) |
duplicatenames |
Allows reusing identifiers in limited scopes |
multilinestrings |
Allows multi-line string literals without manual concatenation |
Note
For the best experience with code completion, we recommend using Lazarus Unleashed - a fork of Lazarus with full support for unleashed mode. If you are using stock Lazarus, enable the mode via -Munleashed in the project's Custom Options rather than placing {$mode unleashed} directly in the source file, to avoid autocomplete issues and incorrect code insight behavior.
Activate: {$modeswitch statementexpressions} (or use {$mode unleashed})
Allows using if, case, and try as expressions that return a value, enabling a more functional and concise coding style. All branches must return values of the same type.
Traditionally, if, case, and try are statements - they perform actions but don't produce a value. With statement expressions, they can be used on the right side of an assignment, as function arguments, or anywhere an expression is expected.
var
s: string;
begin
s := if 0 < 1 then 'Foo' else 'Bar';
// s = 'Foo'
end.Chained if-expressions work as expected:
s := if x > 100 then 'large' else
if x > 10 then 'medium' else
'small';Only one branch is evaluated - side effects in the other branch are never triggered:
function expensive: string;
begin
inc(counter);
result := 'computed';
end;
// ...
s := if condition then 'fast' else expensive;
// expensive is only called when condition is falsetype
TMyEnum = (mefirst, mesecond, melast);
var
s: string;
begin
s := case mesecond of
mefirst: 'Foo';
mesecond: 'Bar';
melast: 'FooBar';
end;
// s = 'Bar'
end.Ranges work too:
s := case x of
0: 'zero';
1..9: 'single digit';
else 'large';Note
When using enums, all values must be covered - otherwise the compiler will reject it. When using integer/ordinal ranges, provide an else clause.
Evaluates a function call and returns a fallback value if an exception occurs:
function conditionalthrow(doraise: boolean): string;
begin
result := 'OK';
if doraise then raise TObject.Create;
end;
var
s: string;
begin
s := try conditionalthrow(false) except 'Error';
// s = 'OK'
s := try conditionalthrow(true) except 'Error';
// s = 'Error'
// match specific exception types:
s := try conditionalthrow(true) except on o: TObject do 'TObject' else 'Error';
// s = 'TObject'
end.Note
The try expression must contain a function call - try 'literal' except ... is not valid.
Activate: {$modeswitch inlinevars} (or use {$mode unleashed})
Declare variables at the point of use inside begin..end blocks instead of in a separate var section at the top. Supports explicit types and type inference.
In standard Pascal, all variables must be declared in a var section before the begin keyword. Inline variables let you declare them exactly where they are needed, reducing visual distance between declaration and use, and enabling type inference from the initializer.
begin
// explicit type, no initializer
var x: integer;
x := 10;
// explicit type with initializer
var y: integer := 42;
// type inference - compiler deduces integer from the literal
var z := 100;
// string inference
var s := 'hello';
// multiple variables of the same type
var a, b: integer;
a := 1;
b := 2;
end.var
sum: integer;
begin
sum := 0;
// explicit type
for var i: integer := 1 to 5 do
sum := sum + i;
// type inference
for var j := 1 to 5 do
sum := sum + j;
end.var
arr: array[0..2] of integer = (10, 20, 30);
sum: integer;
begin
sum := 0;
for var item in arr do
sum := sum + item;
// sum = 60
end.Note
Inline variables have the same scope as regular local variables - they are visible from the point of declaration until the end of the enclosing routine. They are not block-scoped.
Activate: {$modeswitch arrayequality} (requires arrayoperators to also be active; both are enabled in {$mode unleashed})
Adds support for = and <> comparison operators between arrays.
Standard Free Pascal with arrayoperators allows + (concatenation) on dynamic arrays, but does not allow direct equality comparison. This modeswitch fills that gap - you can compare two arrays element-by-element using = and <>.
{$mode unleashed}
var
a, b: array of integer;
begin
a := [1, 2, 3];
b := [1, 2, 3];
if a = b then
writeln('Arrays are equal'); // this is printed
b := [1, 2, 4];
if a <> b then
writeln('Arrays are different'); // this is printed
end.Activate: {$modeswitch nortti}
Important
This modeswitch is not enabled by default in unleashed mode. It must be opted into explicitly.
When enabled, all RTTI strings (type names of custom structures like records, classes, etc.) are stripped from the binary - they are replaced with empty strings. RTTI structures still exist and cannot be fully removed, but the most obvious fingerprint - plain-text type identifiers - is gone.
Sometimes one may want to avoid exposing an application's internal structure, especially when a simple ASCII dump can reveal type names and identifiers, and with them, the true purpose of the program.
For instance, in the context of game cheats, embedding a name like TGameWallhack in the binary can immediately reveal the nature of the software.
📄
|
| Standard | With {$modeswitch nortti} |
|---|---|
Offset Size String acf0 10 0123456789ABCDEF af20 29 FPC 3.3.1 [2025/06/18] for x86_64 - Win64 b0d1 13 TMyAwesomeCheatBase b1c1 0b TGameAimbot b2a9 0d TGameWallhack b3b8 0c Enemy Player b3d8 0d Another Enemy b3ea 13 TMyAwesomeCheatBase b418 0b MyCoolCheat b47a 0b TTargetList b4aa 0b MyCoolCheat b4c2 0b TGameAimbot b512 0b TGameAimbot b538 0b MyCoolCheat b552 0d TGameWallhack b57a 0b MyCoolCheat b5bb 0b MyCoolCheat |
Offset Size String acf0 10 0123456789ABCDEF af20 29 FPC 3.3.1 [2025/06/18] for x86_64 - Win64 b398 0c Enemy Player b3b8 0d Another Enemy |
Type names like TGameAimbot, TGameWallhack, or MyCoolCheat are no longer present, making the binary significantly less identifiable at first glance. Only actual string data (like player names) remains.
Compiling a typical LCL application with nortti enabled will likely result in a startup failure, because code such as:
application.createform(TForm1, form1);will search for "" (empty string) in the resources instead of TForm1, and fail.
Two ways are provided to selectively whitelist identifiers that should remain visible:
1. {$expose} directive - placed before declarations to preserve their names:
{$expose} TForm1 = class(TForm)
// ...
end;2. {$rttiwhitelist ID1 ID2 ...} with multiple identifiers - used to retain specific identifiers:
{$rttiexpose TForm1 TForm2}Wildcards can be used:
{$rttiwhitelist TForm* ...}Note
The {$modeswitch nortti} directive works on a per-unit basis. You can enable it only in the units where you want to hide type names, while leaving it disabled in others - for example, in units that contain forms or require RTTI to function correctly.
Note
This section covers installing FPC Unleashed (the compiler) paired with stock Lazarus. A dedicated installation guide for Lazarus Unleashed - which includes full code completion support for unleashed mode - is currently being written. In the meantime, feel free to explore the Lazarus Unleashed repository and try it out yourself.
- Download fpcupdeluxe and run it once to generate the
fpcup.inifile. - Edit
fpcup.iniand add the following under[ALIASfpcURL]:
[ALIASfpcURL]
fpc-unleashed.git=https://github.com/fpc-unleashed/freepascal.git- Reopen fpcupdeluxe, uncheck GitLab, and select
fpc-unleashed.gitas your FPC version. - Choose any Lazarus version you like.
- Click Install/update FPC+Lazarus.
- Optionally install cross-compilers via the
Crosstab.
- Make sure your existing FPC+Lazarus was installed with fpcupdeluxe.
- In your installation directory, delete or rename the
fpcsrcfolder. - Clone the FPC Unleashed repo into the
fpcsrcdirectory:
git clone https://github.com/fpc-unleashed/freepascal.git fpcsrc- In fpcupdeluxe, go to Setup+, check FPC/Laz rebuild only, and confirm.
- Click Only FPC to rebuild the compiler and RTL.
- Optionally install cross-compilers via the
Crosstab.
We welcome bold ideas and experimental features that push Pascal forward.
FPC Unleashed is a home for innovation - if you have built a language feature that was "too experimental" or "not standard enough" for upstream, this is where it belongs.
- New language ideas - propose modeswitches, syntax extensions, or compiler enhancements via GitHub Issues or Discussions. Even if you don't have an implementation yet, a well-described idea with use cases is valuable.
- Complete, high-quality implementations - we accept pull requests for new language constructs, compiler enhancements, and RTL improvements. We expect production-grade code: clean implementation, proper test coverage, and documentation of the feature.
We do not accept minor convenience patches, trivial reformats, or small tweaks that only scratch a personal itch. Every change to a compiler carries weight - if you are contributing code, it should be a meaningful feature or fix that benefits the broader community.
