Inline Variable Declaration

From RAD Studio
Jump to: navigation, search

The Delphi language in 10.3 allows more flexibility in the declaration of local variables. Until now, following classic Pascal language rules, all variable declarations had to be done in a var block written before the beginning of a function, procedure or method:

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

The new inline variable declaration syntax allows you to declare the variable directly in a code block (allowing also multiple symbols as usual):

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

procedure Test2;
begin
  var I, K: Integer;
  I := 22;
  K := I + 10;
  ShowMessage (K.ToString);
end;

While this might seem a limited difference, there are several side effects of this change. One is that declaration and initialization can be done in a single statement. A second side effect is that you can declare variable as needed in a complex code block, with limited scope (as the variable is visible only from the position of its declaration, and you don’t need to have a variable declared and not initialized for portion of the code).

Scope of Inlined Variables

A third side effect is that the declaration is allowed also within a second level begin-end block and the scope is limited to that block.

procedure Test; // declaration and initialization in a single statement
begin
  var I: Integer := 22;
  ShowMessage (I.ToString);
end;

procedure Test1; // multiple inline declarations (symbols declared when used)
begin
  var I: Integer := 22;
  var J: Integer;
  J := 22 + I;
  var K: Integer := I + J;
  ShowMessage (K.ToString);
end;

procedure Test2; // scope limited to local block
begin
  var I: Integer := 22;
  if I > 10 then
  begin
    var J: Integer := 3;
    ShowMessage (J.ToString);
  end
  else
  begin
    var K: Integer := 3;
    ShowMessage (J.ToString); // COMPILER ERROR: “Undeclared identifier: ‘J’”
  end;
end;

As you can see in the last code snippet above, a variable declared inside a begin-end block is visible only in the specific block, and not after the block has terminated. At the end of the if statements, J and K won’t be visible any more.

The effect is not limited only to visibility. A managed variable, like an interface reference or a record, will be properly cleaned up at the end of the block, rather than at the end of the procedure or method:

procedure Test99;
begin

  // some code

  if (something) then
  begin
    var Intf: IInterface = GetInterface; // Intf.AddRef
    var MRec: TManagedRecord = GetMRecValue; // MRec.Create + MRec.Assign
    UseIntf(Intf);
    UseMRec(MRec);
  end; // Intf.Release and MRec.Destroy are implicitly called at end of scope
  
  // more code

end; // no additional cleanup

Type Inference for Inlined Variables

Additionally, the compiler can now in several circumstances infer the type of a variable at ints line declaration location, by looking to the type of the value assigned to it.

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

The type of the r-value expression (that is, what comes after the :=) is analyzed to determine the type of the variable. Some of the data types are “expanded” to a larger type, as in the case above where the numeric value 22 (a ShortInt) is expanded to Integer. As a general rule, if the right hand expression type is an integral type and smaller than 32 bits, the variable will be declared as a 32-bit Integer. You can use an explicit type if you want a specific, smaller, numeric type.

Also notice that only a single identifier can be declared without a value type (differently from general variable declarations and inline declarations).

Now while this feature can save you a few keystrokes for an Integer or a string, variable type inference becomes fairly nice in case of complex type, like instances of generic types. In the code snippet below, the types inferred are “TDictionary<string, Integer>” for the variable MyDictionary and “TPair<string, Integer>” for the variable APair.

procedure NewTest;
begin
  var MyDictionary := TDictionary<string, Integer>.Create;
  MyDictionary.Add ('one', 1);
  var APair := MyDictionary.ExtractPair('one');
  ShowMessage (APair.Value.ToString)
end;

Inline Constants

Beside variables, you can now also inline a constant value declaration. This can be applied to types constants or untyped constants, in which case the type is inferred (a feature that has been available for constants for a long time). A simple example is below:

   const M: Integer = (L + H) div 2; // single identifier, with type specifier
   const M = (L + H) div 2; // single identifier, without type specifier

For Loops With Variable Declaration

A specific circumstance in which you can take advantage of inline variable declarations is with loop statements, including for-to loops and for-in loops.

  for var I: Integer := 1 to 10 do ...
  for var Item: TItemType in Collection do...

You can further simplify the code taking advantage of type inference:

  for var I := 1 to 10 do ...
  for var Item in Collection do ...

This is a case in which having the variable with limited scope is particularly beneficial, as in the sample code below: Using the ‘I’ variable outside of the loop will cause a compiler error (while it was only a warning in most cases in the past):

procedure ForTest;
begin
  var total := 0;
  for var I: Integer := 1 to 10 do
    Inc (Total, I);
  ShowMessage (total.ToString);
  ShowMessage (I.ToString); // compiler error: Undeclared Identifier ‘I’