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 |
tuples |
Anonymous tuple types, literals, and destructuring |
match |
Pattern matching with first-match semantics |
multivarinit |
Initialize several variables of the same type with one value |
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 |
Use AnsiString as the default string type |
underscoreisseparator |
Allow underscores in numeric literals (1_000_000) |
duplicatelocals |
Allow reusing identifiers in limited scopes |
multilinestrings |
Allow multi-line string literals without manual concatenation |
stringordcast |
Cast a string literal to an ordinal type (dword('RIFF')) |
Note
For the best code-completion experience, 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 instead of placing {$mode unleashed} directly in the source file, to avoid autocomplete issues and incorrect Code Insight behavior.
Activate: available in Unleashed mode.
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 loadFromDatabase: string;
begin
result := 'data';
end;
var useCache := false;
var s := if useCache then 'cached' else loadFromDatabase;
// 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.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 the expression. When using integer or 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: available in Unleashed mode.
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.
Note
Untyped numeric inline variables default to a 32-bit signed integer (integer).
Activate: available in Unleashed mode (modeswitch tuples).
Lightweight anonymous record types written in parentheses, with literals, destructuring, comparison, and full record semantics (managed types, copy by value, passing by var/const). Tuples are stored as ordinary internal records, so anything records can do, tuples can do.
function GetPair: (integer, integer);
begin
Result := (10, 20); // positional literal
end;
var
p: (integer, string) := (42, 'hello');
begin
writeln(p._1, ' ', p._2); // 42 hello
writeln(p[0], ' ', p[1]); // same, by constant index
end.function Coords: (x, y: integer);
begin
Exit(x: 10, y: 20); // shorthand inside Exit
end;var
a, b: integer;
begin
(a, b) := GetPair; // unpack tuple into existing vars
end.See unleashed/docs/tuples.md for the full grammar (named/positional mixing, tuple arrays, comparison operators, IDE hints).
Activate: available in Unleashed mode (modeswitch match).
Pattern matching with first-match semantics. Replaces case..of for non-ordinal subjects (tuples, strings, arbitrary expressions) and adds catch-all, fallthrough, condition-based branches, tuple wildcards, and an expression form.
match s of
'hello': writeln('greeting');
'bye': writeln('farewell');
_: writeln('unknown'); // catch-all
end;match
x > 100: writeln('big');
x > 10: writeln('medium');
x > 0: writeln('small');
_: writeln('non-positive');
end;var p: (integer, integer) := (0, 5);
match p of
(0, 0): writeln('origin');
(0, _): writeln('on Y axis'); // matches
(_, 0): writeln('on X axis');
_: writeln('other');
end;var label_: string := match x of
1: 'one';
2: 'two';
_: 'many';
end;See unleashed/docs/match.md for fallthrough mode (match all), leave, range patterns, and exhaustiveness rules.
Activate: available in Unleashed mode (modeswitch multivarinit).
Initialize several variables of the same type with a single value in one declaration. Works in var, typed constants, and inline var. Each variable gets its own independent copy.
var
a, b, c: integer = 42; // global var
ok, done: boolean = false;
const
MinX, MinY, MinZ: integer = 0; // typed constants
procedure Bar;
begin
var p, q: integer := 99; // inline var
var i, j := 10; // inline var with inference
end;The initializer is evaluated once and copied into each variable; a := 100 does not affect b or c. See unleashed/docs/multi-var-init.md for the full evaluation table.
Activate: available in Unleashed mode (modeswitch multilinestrings).
Two delimiter forms let a string literal span multiple source lines without manual + or LineEnding.
const
banner =
`========================================
= FCF Fibonacci Demo =
========================================`;A normal string literal extended to tolerate embedded newlines.
const
sql =
'''
select id, name
from users
where active = 1
''';A Delphi-11-style textblock literal. The opener (''' followed by a newline) and the closer (''' on its own line) must each sit alone; the indentation of the closing delimiter defines the column that gets stripped from every content line.
The two forms differ in tokenization, indentation handling, and how they compose in expressions. See unleashed/docs/multiline-strings.md for the details. (Stock FPC actually accepts these too but never documented them.)
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.
Labels now support indexes.
label
mylabel1,
mylabel2[1, 2, 3],
mylabel3[1..10],
mylabel4['foo', 'bar'];
begin
goto mylabel4['foo'];
writeln('you should not see this');
mylabel1:
mylabel2[2]:
mylabel3[10]:
mylabel4['foo']:
writeln('hello!');
end.Labels no longer need to be declared before use.
begin
goto mylabel;
writeln('you should not see this');
mylabel:
writeln('hello!');
end.Compound assignment is now supported for Pascal operators such as div, mod, and xor, without requiring {$COPERATORS ON}.
var
i: integer = 10;
begin
i div= 2; // equivalent to: i := i div 2
writeln(i); // prints "5"
end.Available: div=, mod=, and=, or=, xor=, shl= and shr= .
Smaller, targeted improvements that unlock Pascal patterns standard FPC modes reject. Each is gated on its own modeswitch (some are on by default in unleashed, others must be opted into):
stringordcast- cast a string literal to an ordinal type at compile time, e.g.dword('RIFF')orword('MZ'). Useful for signature checks. On by default in unleashed.typehelpers-type helper for Ton any named type, not just classes and records.multihelpers- several helpers for the same type visible in one scope (instead of "last one wins").implicitgenerics- Delphi-style implicitgeneric/specializesyntax (TList<T>without keywords). Stock FPC locks this to{$mode delphi}; the modeswitch makes it usable in any mode.
Full descriptions and examples in unleashed/docs/extra-improvements.md.
Each feature has a dedicated reference page in unleashed/docs/ with the full grammar, semantics, edge cases, and IDE notes. Start at the index: unleashed/docs/README.md.
- Download fpcupdeluxe and run it once to generate the
fpcup.inifile. - Edit
fpcup.iniand add the following under[ALIASfpcURL]:
[ALIASfpcURL]
unleashed.git=https://github.com/fpc-unleashed/freepascal.gitAnd, for Lazarus Unleashed (with autocomplete support for some of the new features), add the following under [ALIASlazURL]:
[ALIASlazURL]
unleashed.git=https://github.com/fpc-unleashed/lazarus.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 installation was created 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 considered 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 do not have an implementation yet, a well-described idea with clear 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 clear 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.
