Skip to content

Passing parameters

Michalis Kamburelis edited this page Aug 19, 2017 · 5 revisions

(I originally wrote this as an answer to forum question here: http://forum.lazarus.freepascal.org/index.php?topic=37980 .)

When passing a parameter A to a function / procedure / method, you can declare how it is passed:

procedure DoSomething(A: TSomeData);
procedure DoSomething(var A: TSomeData);
procedure DoSomething(const A: TSomeData);
procedure DoSomething(out A: TSomeData);
procedure DoSomething(constref A: TSomeData);

Various ways to pass the parameter determine:

  • Whether you can change the value of A inside DoSomething (in case of const and constref you cannot),
  • What happens with the variable that was used to initialize the parameter, when you change the A inside DoSomething.
  • How is the A internally passed to the procedure (by copying the value, or by passing a pointer to the value).

The full explanation of this may get a little complicated if you consider all the possible things that TSomeData may be (simple integer/float/enum, a class, a record, an AnsiString...), I tried to keep my answer below simple :) More information e.g. in FPC docs on https://www.freepascal.org/docs-html/current/ref/refse91.html

Passing the value (copy the value being passed to the procedure):

procedure DoSomething(A: TSomeData);

When called as DoSomething(B), the compiler creates a copy of B and places it inside your parameter A. This allows you to freely change the value of A inside the DoSomething implementation, without affecting B.

E.g.

procedure DoSomething(A: Integer);
begin
  A := A + 10;
  Writeln(A);
end;

var
  B: Integer;
begin
  B := 20;
  DoSomething(B); // will write 30
  // B is still 20 here
end.

This is sometimes useful, if you really want to modify A inside DoSomething, without affecting the B. But if you don't want to modify A inside the DoSomething implementation, then using const is usually safer (compiler will not let you modify A inside the procedure) and may be more efficient (the const parameter may be internally passed by only passing a pointer to it, not copying it's value, in case if TSomeData size is larger).

Note that if TSomeData is a class (like TObject, TStringList...) then we're only talking here about copying the reference to the class contents. A and B will still point (and least initially) to the same instance. Things are different in case TSomeData is a record, in which case A and B will be just different values. The rule to remember here is that this "copy" is exactly the same as when you do A := B in code.

As far as efficiency, the compiler must create a copy of the value. So this is not efficient for larger TSomeData (when TSomeData is something larger than a pointer) -- well, unless you really wanted to create a copy, to be able to modify it locally inside DoSomething.

Passing by a pointer to the variable:

procedure DoSomething(var A: TSomeData);

When called as DoSomething(B), the compiler internally passes a pointer to B. Accessing A (reading, writing) is now the same thing as accessing B. You can change A inside DoSomething implementation, and it immediately changes also B.

procedure DoSomething(var A: Integer);
begin
  A := A + 10;
  Writeln(A);
end;

var
  B: Integer;
begin
  B := 20;
  DoSomething(B); // will write 30
  // B is now 30
end.

So this is very efficient (only a pointer is passed, regardless of what TSomeData is). Whether you want it -- depends on what you wanted to achieve.

Constant parameter (cannot modify it inside the procedure, doesn't matter how it's internally passed):

procedure DoSomething(const A: TSomeData);

In this case, you cannot change A inside DoSomething.

It is efficient, and the compiler decides whether internally a pointer to TSomeData is passed, or a just a copy of TSomeData is made. In this sense, the compiler internally decides between doing something like option 1. (copying the value, which makes sense if it's something smaller than the pointer) or 2. (passing the pointer to the value). But what you can do with A differs from both option 1. and option 2. (as now you cannot modify "A" at all inside DoSomething).

Out parameter, that must be set inside the procedure implementation:

procedure DoSomething(out A: TSomeData);

out A is similar to var A, but it tells the compiler that the initial value of the parameter (when the DoSomething is called) doesn't matter, and the implementation of DoSomething should always set it. It will also change the warnings/hints generated by the compiler.

It may make the parameter passed differently, but in practice it is always passed internally using a pointer. So this is as efficient as option 2., var A.

Note that in case of a single parameter to the procedure, it's usually more comfortable to just define it as a function: function DoSomething: TSomeData;. But using out is useful if you want to return two or more values.

Constant parameter that must be passed by a pointer:

procedure DoSomething(constref A: TSomeData);

constref A is like const A, but forces the compiler to pass a pointer to A internally. This is usually not needed (using "const" leaves compiler more flexibility, and is usually as good for you). This is as efficient as option 2., var A.