CONST parameters vs non-const

Everybody know that in Delphi there are four types of parameters:

  • Standard parameter without any prefix;
  • VAR parameters;
  • CONST parameters:
    • with [Ref] decoration;
    • without decoration;
  • OUT parameters.

But let me just refresh your memory a bit 🙂

Standard

When no prefix is defined for the parameter the compiler passes it by value (simple types) or by reference (string, fixed array, dynamic arrays and records) with copy on write technique. And we still have an ability to assign a value to the parameter inside of the function/procedure, but that only affects the value in the stack which was pushed there before.

procedure Test(X: Integer);
begin
  X := 10;
end;

var
  X: Integer = 1;
begin
  Test(X);
  Writeln(X); // Outputs 1
end.

When we define such a parameters we can pass the constants as well.

Var

Var parameters are passed to the routine always by reference. So once the value is assigned to the parameter it changes the source instance/memory block. You should use the var parameters only when you actually modify the value of the variable you pass to the routine. When you access and modify the fields of the object or interface you do not need to mark parameter as var. When other developer looks at your code and sees the var prefix he will be confused – as var means that the value can be changed from inside of the routine! And also once you define the parameter as var you cannot pass the constants to the routine.

procedure Test(var X: Integer);
begin
  X := 10;
end;

var
  X: Integer = 1;
begin
  Test(X);
  Test(10); // Not allowed
  Writeln(X); // Outputs 10
end.

Out

The out parameters are the same as var parameters except that the out prefix indicates that the input value is not respected (for managed types, for simple types that will be just assumption as the value is not set to default) and the parameter value is initialized with the default value for managed types (such as string, dynamic array or interface).

procedure TestX(out X: Integer);
begin
  Writeln(X); // Will output 1
  X := 10;
end;
procedure TestS(out X: String);
begin
  Writeln(X);  // Will output empty string
  X := 'Inside';
end;

var
  X: Integer = 1;
  S: String = 'Initial';
begin
  TestX(X);
  TestX(10); // Not allowed
  Writeln(X); // Outputs 10

  TestS(S);
  Writeln(S); // Outputs 'Inside'
end.

Const

The const parameters are almost the same as standard parameters, but have some differences. It is not allowed to set the value to the const parameter inside of the function/procedure either pass it as a var/out parameter to other routines. The const parameters are passed by value (simple types) or by reference (string, fixed array, dynamic arrays and records. However this depends on the size of the parameter. If the size of the parameter is bigger than a certain amount of bytes then it is passed by reference instead) but without copy on write – remember, those are marked read-only and we cannot assign values to them or pass them to var parameters of other routines. You can specify [Ref] before the parameter to force the compiler to pass the value by reference – so it will be the same as var parameter except that the routine cannot modify the value directly via assignment. But be warned that const [Ref] parameters cannot be captured by the anonymous methods (lambdas) – as it is with “Result” in functions.

procedure Test(const X: Integer);
begin
  Writeln(X);
  X := 10; // Not allowed
  Readln(X); // Not allowed
end;

var
  X: Integer = 1;
begin
  Test(X);
  Writeln(X); // Outputs 1
end.

But there is one more special thing for const parameters with reference counted types – the reference count is not affected when passing the variables to the const parameters. So if we pass an interface variable to a normal parameter on enter to the routine the reference count will be incremented and on leaving it – decremented. But that does not happen with const parameters! See a demo project in the end of the post.

So there is the demo project demonstrating the reference count management:

program ParameterTypes;

{$APPTYPE CONSOLE}

type
  ITest = interface
    ['{D200C91B-3D4D-45AB-A1E0-A803C6A03292}']
  end;

 TTest = class(TInterfacedObject, ITest)
 private
   function _AddRef: Integer; stdcall;
   function _Release: Integer; stdcall;
 end;

function TTest._AddRef: Integer;
begin
 Writeln(FRefCount + 1);
 Result := inherited;
end;

function TTest._Release: Integer;
begin
 Writeln(FRefCount - 1);
 Result := inherited;
end;

procedure TestStandard(X: ITest);
begin
 Writeln('Inside standard');
end;

procedure TestVar(var X: ITest);
begin
 Writeln('Inside var');
end;

procedure TestConst(const X: ITest);
begin
 Writeln('Inside const');
end;

procedure TestConstRef(const [Ref] X: ITest);
begin
 Writeln('Inside const');
end;

procedure TestOut(out X: ITest);
begin
 Writeln('Inside out');
end;

var
  X: ITest;
begin
  Writeln(StringOfChar('-', 40));
  Writeln('Creating and calling with standard parameter type:');
  X := TTest.Create;
  TestStandard(X);
  X := nil;
  Writeln(StringOfChar('-', 40));

  Writeln('Creating and calling with var parameter type:');
  X := TTest.Create;
  TestVar(X);
  X := nil;
  Writeln(StringOfChar('-', 40));

  Writeln('Creating and calling with const parameter type:');
  X := TTest.Create;
  TestConst(X);
  X := nil;
  Writeln(StringOfChar('-', 40));

  Writeln('Creating and calling with const [Ref] parameter type:');
  X := TTest.Create;
  TestConstRef(X);
  X := nil;
  Writeln(StringOfChar('-', 40));

  Writeln('Creating and calling with out parameter type:');
  X := TTest.Create;
  TestOut(X);
  X := nil;
  Writeln(StringOfChar('-', 40));

  Readln;
end.

The output of this program is following:

Inside standard
1
0
----------------------------------------
Creating and calling with var parameter type:
1
Inside var
0
----------------------------------------
Creating and calling with const parameter type:
1
Inside const
0
----------------------------------------
Creating and calling with const [Ref] parameter type:
1
Inside const
0
----------------------------------------
Creating and calling with out parameter type:
1
0
Inside out
----------------------------------------

The source is available on GitHub.

Further reading:

6 comments

  1. “The const parameters are passed by value (simple types) or by reference (string, fixed array, dynamic arrays and records)”

    That is not entirely true as it depends on the size of the value. If it is small enough it is indeed being passed by value.

    Here is a small example to show that: http://pastebin.com/4HALLNFm
    As you can see although I used pointer access to write to the const argument it only got changed for the large record because that one was in fact being passed as reference,

    Like

Leave a Reply