5 Reasons to Use Inline Variables in Delphi

Inline variables declaration is a feature introduced in Delphi Rio 10.3. What is it?

In short, it is the possibility to declare a variable in any line of your code. That is, you can declare a variable this way, within the begin..end block:

procedure Test;
begin
  var I: Integer;
  I := 22;
  ShowMessage (I.ToString);
end;

A lot of people already understood how this feature works, but did not understand why it is interesting. In this article, I will show you this new feature with a focus on the advantages it brings.

1. Organizes your code

The variable is only accessible from the point it is declared. For many people this better organizes the code in a large method, because it is possible to know better where that variable is being used. Consider the following code:

procedure Test;
var 
  A, B, C, D, E: Integer;
  Found, Done, Excluded: Boolean;
  Text: string;
begin
   // many
   // lines
   // of
   // code
end;

It may be confusing to know where all of these variables are used, when they are being initialized, if it has been set a value before, etc. In the following code, we know that the Text variable, for example, does not exist at the beginning of the code, and that it is only used at the end. No code changed its value bfore that part of the code:

procedure Test;
begin
   var A, C: Integer;
   // We cannot use Text here
   // lines
   // of
   // code
   
   var Text: string;
   // Text can only be used here
end;

2. Minimize bugs

Have you ever done something like this:

procedure Test;
var I: Integer;
begin
  for I := 0 to Count - 1 do
    Process;
  DoSomethingWithI(I);
end;

That is, using the for variable after the loop is finished. This is not safe, and although the compiler raises a warning for this, many people ignore it. By declaring the for variable inline, it will be only valid inside the for, and using it outside the block will result in a compilation error:

procedure Test;
begin
  for var I: Integer := 0 to Count - 1 do
    Process;
  DoSomethingWithI(I); // Compile error!!!
end;

The benefit from the code above comes from the fact that the scope of the variable is limited to the block in which they are declared. That minimizes the chance of errors. For example, suppose you have a code like this:

procedure Test;
var I: Integer;
begin
  I := CalculateSomething;
  Persist(I);
  // many lines below...
  Log(I);
end;

Then you eventually need to refactor the code in a way that the first part only executes under a specified condition. You think that variable I is only being used there, and do something like this:

procedure Test;
var I: Integer;
begin
  if Condition then
  begin
    I := CalculateSomething;
    Persist(I);
  end;
  // many lines below...
  Log(I);
end;

There, you forgot the last line and maybe the value of I is not what you expect. Changing the scope of your variable to the block will generate compilation errors if the variable is used outside the block, which will show you the problem immediately, so you can make a decision:

procedure Test;
begin
  if Condition then
  begin
    var I: Integer;
    I := CalculateSomething;
    Persist(I);
  end;
  // many lines below...
  Log(I); // Compile error!
end;

3. Less typing

Who does not want more productivity? If you can type a bit less to declare a variable, why not? You can now declare and initialize a variable at the same time:

procedure Test;
begin
  var I: Integer := 22; 
  ShowMessage (I.ToString);
end;

But not only that. There is also type inference, which means that in most cases you do not need to include the variable type when declaring it. Just initialize the variable with a value and Delphi will know the variable type.

Looks like it’s not a big deal? Imagine a case where the variable type is using heavy generics:

procedure NewTest;
var
  MyDictionary: TObjectDictionary<string, TObjectList<TMyAmazingClass>>;
  Pair: TPair<string, TObjectList<TMyAmazingClass>>;
  List: TObjectList<TMyAmazingClass>;
begin
  MyDictionary := TObjectDictionary<string, TObjectList<TMyAmazingClass>>.Create;
  MyDictionary.Add('one', CreateList);
  Pair := MyDictionary.ExtractPair('one');
  List := Pair.Value;
  ShowMessage(List.Count.ToString);
end;

Using inline variable and type inference, you can rewrite the code this way:

procedure NewTest;
begin
  var MyDictionary := TObjectDictionary<string, TObjectList<TMyAmazingClass>>.Create;
  MyDictionary.Add('one', CreateList);
  var Pair := MyDictionary.ExtractPair('one');
  var List := Pair.Value;
  ShowMessage(List.Count.ToString);
end;

Better, isn’t it?

4. Increases performance

The fact that the variables belong to a more limited scope (within a begin..end block) can even increase code performance!

You can see more details in this excelent article: Inline Variables can increase performance. In summary: the variable will initialized only if the code execution enters the block, and finalized only upon block exit. In this code, for example:

procedure TestInlineVars(const ACondition: Boolean);
begin
  // BEFORE
  if (ACondition) then
  begin
    var S := 'Inline String';
    var I: IInterface := TInterfacedObject.Create;
    var F: TFoo;
    F.S := 'Managed Record';
  end;
  // AFTER
end;

Variables S, I and F are managed types (string, interface and record). The compiler automatically adds initialization and finalization code for them.

If you call TestInlineVars procedure a million times, it will have a big impact. However with the code above, the variables will only be initialized if ACondition is true and the block is actually executed. Less unnecessary code being executed.

5. Makes it easier to use conditional directives

This feature can help even in small things. This article brought my attention: Unexpected Benefit of Inline Variables: Conditional Blocks.

If you use compiler directives where you declare and use different variables for each situation, you have also wrap variable declarations around a compiler directive as well:

procedure DoesSomething;
var
  {$IFDEF CASE1}
  var1: Integer;
  {$ENDIF}
  {$IFDEF CASE2}
  var2: Integer;
  {$ENDIF
begin
  {$IFDEF CASE1}
  // use var1
  {$ENDIF}
  {$IFDEF CASE2}
  // use var2
  {$ENDIF}
end;

Boring, huh? In my opinion this is easier:

procedure DoesSomething;
begin
  {$IFDEF CASE1}
  var1: Integer;
  // use var1
  {$ENDIF}
  {$IFDEF CASE2}
  var2: Integer;
  // use var2
  {$ENDIF}
end;

I believe that inline variables still brings other subtle benefits in specific situations that are not listed here. If you can think of any other benefit, leave your comment. If you do not agree and you think inline variables are not good news for Delphi, leave your comment as well. Just do not forget one thing: if you didn’t like it, simply don’t use it!

12 thoughts on “5 Reasons to Use Inline Variables in Delphi”

  1. 1 reason NOT to use inline vars (at the moment): Error/CodeInsight does not recognize them, thus ANY productivity tools (“find declaration”, “refactoring”, etc.) won’t work anymore.

    • Thanks for contributing, Dennis. Indeed, that’s one disadvantage. To be fair, error/codeinsight doesn’t work with lots of other things and many users simply disable it. Refactor is an issue indeed. Pros and cons have to be weighted and left for each one to decide. I hope error insight and refactoring will get fixed with the upcoming Language Server Protocol, planned to be included in Delphi 10.4.

  2. While I agree there are some advantages to using in-line variables I think your argument for better 1) code organization isn’t a strong one. If you have many lines of code in a method and cannot tell where the variables are used or initialized, then you should consider refactoring and using blocks (without inline vars) to organize and document your code better.

    Under 2) while you may be right that people often ignore warnings, they should not! Warnings should be considered errors and resolved before committing code. It speaks to a lack of attention to detail that has no place in computer programming. The idea that success = it compiles may have been acceptable in the 80s, but not now. If old style code forces people to look at and eliminate warnings maybe it’s a good thing!

    I too hope that the Delphi LSP integration addresses a lot of the known issues with in-line vars as well as refactoring, code insight, code completion, class completion and refactoring. Unfortunately, unless everyone shares the same code DOM or AST a lot of the productivity tools like MMX, GExperts, CNPack etc will still lag behind new language features.

    To think people often consider me a pessimist!

    • Hi Larry. Regarding 1, I think it’s a matter of get used to is and also personal taste of course. Indeed for some people, it’s not more organized. About warnings, I 100% agree with you. But real world is different. I’ve seen projects which raise literally thousands of hints and warnings. In those cases it’s just not feasible to review them in short term. Wrong, but reality. Those were just some examples I could think of, maybe there are more that don’t even raise warnings. Thanks for commenting and let’s hope for great improvements!

      • A function/procedure should do one thing and one thing only, and do it well, code delegation to break up complex tasks results in less, more readable code, has worked for me for 47 years, I can still read stuff I wrote in RPGII, in 1983.

        • I personally agree with you, Andre. But people are different, and actually sometimes, out of laziness, I also get myself writing long methods.

  3. It was only a matter of time before Delphi caught up with the other languages 🙂 seriously though, the simplification of conditionals would be a big plus for me.

  4. I made a project with Rio and used inline variables. It worked fine up to some point and I got strange errors and the code looked fine. After removing inline vars it worked flawless.
    I myself will be careful with them

  5. “… I got strange errors…” sounds like a message from a typical user and not the words a developer should use.

    Like Wagner asks: what were those error messages?

Leave a Comment