Jump to content
Mike Torrettinni

Out parameter is read before set

Recommended Posts

I have a few examples of methods that return list of values in out parameter, like this:

function AddItems(..., out aList: TList): boolean;
begin
  for loop
    if condition then
    begin
      if aList = nil then
        aList := aList.Create; // only create aList if any items gets added
      aList.Add();
    end
  Result := aList <> nil;
end;

Of course if no items are added, return false.

 

Pascal analyzer says: Out parameter is read before set, which is true, but it only checks for nil. Is this a valid alert and I should redesign my methods, or just ignore it?

Share this post


Link to post

Pascal Analyzer is trying to tell you: "Your method misses to initialize aList before entering the loop, because in case the loop is empty or condition is always false you end up with an uninitialized out parameter aList!"

 

So, yes, it is a valid alert and although you may get away with it in most cases, it may fire back sometimes. It should be possible to create a use case where it fails.

  • Like 2
  • Thanks 1

Share this post


Link to post

And here it is:

program Project804;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Classes;

function AddItems(const aArray: array of string; out aList: TStrings): boolean;
var
  S: string;
begin
  { can we really omit this? No! }
  //aList := nil;
  for S in aArray do
    if S.Length > 0 then
    begin
      if aList = nil then
        aList := TStringList.Create; // only create aList if any items gets added
      aList.Add(S);
    end;
  Result := aList <> nil;
end;

var
  lst: TStrings;
begin
  try
    lst := nil;

    try
      if AddItems(['Hello', 'World'], lst) then
        Writeln('True')
      else
        Writeln('False');
      if lst <> nil then
        Writeln(lst.Text);
    finally
      { uncommenting this will raise an invalid pointer exception later }
      //lst.Free;
    end;

    try
    { Although being an out parameter, the current lst instance will be used inside AddItems }
      if AddItems(['Delphi'], lst) then
        Writeln('True')
      else
        Writeln('False');
      if lst <> nil then
        Writeln(lst.Text);
    finally
      lst.Free;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

 

  • Thanks 1

Share this post


Link to post

I see now, good example, thanks!

 

And you are right, I got away with it because I didn't handle correctly the list outside the called function. It worked because I had .Free only on function result = True. So, it was a bit messed up the whole thing, but your example exposed my wrong way of doing it.

 

Edited by Mike Torrettinni

Share this post


Link to post

The message tells you: The out parameter should be considered as not-existing inside the method, so it cannot be used before its creation.

If you want to use it like that you maybe better consider to use a var parameter.

Share this post


Link to post
4 hours ago, Rollo62 said:

The message tells you: The out parameter should be considered as not-existing inside the method, so it cannot be used before its creation.

You mean non-existing outside the method before the call, right? If yes then I see what you mean, and is only used outside after the function call.

Share this post


Link to post
Quote

You mean non-existing outside the method before the call, still means as not-existing inside the method, so it cannot be used before its creation.

type
  TJobflag = (jfstart, jfAdd, jfLoaded, jfDone);
  Tcompareflag = (cfCaps, cfDupes);
  Jobs = set of TJobflag;
  Compares = set of Tcompareflag;


function AddItems2(const aArray: array of string; var aList: TStrings; aCompareSet: Compares)
  : TJobflag;
var
  jobSpecification: Tcompareflag;
  S: string;
  RC: Boolean;
begin
  result := jfDone;
  RC := False;
  for S in aArray do
  begin
    for jobSpecification in aCompareSet do
    begin
      RC := (jobSpecification = cfCaps)
    	 // and // compare S for Caps;
      RC := RC or (jobSpecification = cfDupes) //and dupes found		 
	
	  If RC then begin
        if not assigned(aList) then
          aList := TStringList.create 
	    aList.add(S);
      end;
    end;
    result := jfLoaded;
  end;
end;

    try
      if AddItems2(['Hello', 'World'],lst, [cfCaps]) = jfLoaded then
        Writeln('True')
      else
        Writeln('False');
      if lst <> nil then
        Writeln(lst.Text);
    finally
      { uncommenting this will raise an invalid pointer exception later }
      // lst.Free;
    end;

Or

procedure loadLists 
  aList := TStringList.create 
  result := jfDone;

  for S in aArray do
  begin 
    for jobSpecification in aCompareSet do
    begin
      RC := (jobSpecification = cfCaps // and // compare S for Caps;
      RC := RC or (jobSpecification = cfDupes) //and dupes found		 
	
	  If RC then aList.add(S);
      If RC then jobStatus := jfLoaded;
    end;
  end;
end;	

// future 
Procedure processLists

Abover my spin on it use enums to determine status testing for list = nil does not tell if list need was ever tested.

  • Like 2

Share this post


Link to post
1 hour ago, Mike Torrettinni said:

You mean non-existing outside the method before the call, right? If yes then I see what you mean, and is only used outside after the function call.

Yes and no, I mean "out" is giving back a fresh variable to its caller.

The state before should be considered undefined, it can exist, can have values, can be nil.

I would say that in the 1. line inside the method, this does not exist, since the out parameter shall give back a new parameter.

Whatever it was before shall not matter.

 

From my opinion this is what "out" should originally stands for, even if it maybe can be misused somehow.

 

On the other side, "var" brings in an existing parameter, inside the method, which can be used or changed at will.

  • Thanks 1

Share this post


Link to post
On 6/5/2021 at 3:28 PM, Mike Torrettinni said:

Pascal analyzer says: Out parameter is read before set, which is true, but it only checks for nil. Is this a valid alert and I should redesign my methods, or just ignore it?

It is valid alert. You need to initialize out parameters before they are used. Only managed out types will be automatically initialized.

 

When you mark parameter as out, you are saying I will properly initialize that variable (setting to nil is also proper initialization in this context) If you don't initialize that parameter inside the method, then this is a bug and your method signature is telling a lie.

 

If you want to initialize variables outside the method, then you should use var parameter. The fact that Delphi automatically initializes only managed out types, is just implementation detail of the compiler. In theory it is behavior that may change, most likely it will not, but out parameter is meant for output, compiler is free to discard its value in the function prologue.

  • Like 3
  • Thanks 1

Share this post


Link to post

Out params in Delphi kinda suck because the compiler does nothing to enforce out semantics. Compare and contrast with C#. 

  • Like 4

Share this post


Link to post

I keep coming back to this thread and I think I finally see where I was wrong, I assumed that empty List (but created!) is a valid variable for out parameter, because I only get values added to the list and returned.

So, when I created a List outside and pass it as out parameter, I didn't realize it will not pass List but a empty placeholder for a returned value, so nil List that needs to be created first, before any values are added.

 

Even the documentation says it:

"An out parameter, like a variable parameter, is passed by reference. With an out parameter, however, the initial value of the referenced variable is discarded by the routine it is passed to. The out parameter is for output only; that is, it tells the function or procedure where to store output, but does not provide any input. "

 

I assumed empty list is empty, so all good, nothing to discard.

 

In this example we can see that the actual List variable doesn't get destroyed/discarded as it stays valid after the method call:

 

uses
  System.SysUtils, System.Generics.Collections;

procedure Call(out aList: TList<string>);
begin // no changes, no returned value in out parameter
end;

var vList: TList<string>;

begin
  vList := TList<string>.Create;
  vList.Add('test');
  Call(vList);
  vList.Add('test2'); //  here list will have both strings, 'test' and 'test2'
end.

Interesting.

 

 

 

 

Share this post


Link to post
6 minutes ago, Mike Torrettinni said:

With an out parameter, however, the initial value of the referenced variable is discarded by the routine it is passed to.

It would be nice if that documentation was accurate, but it's not. You will have no problems passing a non-nil instance into that function using an out param. You'll find, at least for non-managed types, that out and var parameters behave identically.

  • Like 2

Share this post


Link to post
On 6/6/2021 at 4:11 PM, Dalija Prasnikar said:

The fact that Delphi automatically initializes only managed out types, is just implementation detail of the compiler.

Yes, now I understand. String variable gets cleared while List and integer don't:

 

uses
  System.SysUtils, System.Generics.Collections;


procedure Call(out aList: TList<string>; out aStr: string; out aInt: integer);
begin
end;

var vList: TList<string>;
    s: string;
    i: integer;

begin
  vList := TList<string>.Create;
  vList.Add('test');
  s := 'delphi';
  i := 19;
  Call(vList, s, i);
  vList.Add('test2'); //  here list will have both strings in 'test' and 'test2'
  // s will be cleared!!
  // i remains 19

 

Share this post


Link to post

Very glad to everyone that brought this in my attention. Did not know all this.  Now just have to remember this.

 

-Tee-

  • Like 1

Share this post


Link to post

Am I the only person who treats out params as "write-only" vars?

 

I get that the compiler doesn't always handle them properly when you abuse them.

 

IRL code, I use them when I need to return more than one value from a method.

 

Saying you can pass in an initial value to an out param is like saying you can pass in a default return value (result) to a function -- and there's probably a way to do that where the compiler won't complain about it either.

 

You can also call a method with no parameters at all and the compiler won't complain. And even when you do that, it's possible to pass in values for those parameters!

 

This is to maintain backward compatibility with ancient syntactic crap in very very old versions of TurboPascal that probably have their roots in even older compilers (USCD Pascal, perhaps?).

 

This is what Pascal Analyzer was created for -- to show you crappy code expressions that, while legal, should not be used. I seem to recall that PA's list of stuff is around 3x bigger than Delphi's.

 

PA does for Delphi as what lint does for 'c'.

 

Speaking of which ... who remembers the ads in programming mags by the guys who published a sooper-dooper version of lint (maybe lint++), under a heading like, "Can you find the bug?"

  • Like 1

Share this post


Link to post
6 hours ago, David Schwartz said:

Speaking of which ... who remembers the ads in programming mags by the guys who published a sooper-dooper version of lint (maybe lint++), under a heading like, "Can you find the bug?"

image.thumb.png.28173de0f53f177f61ff680398041202.png

  • Like 1

Share this post


Link to post

Gimpel Software and PC Lint seem to be still around, and I still have a copy, though a version too old to run on Windows these days.  I met Jim Gimpel a few times back in the days when one travelled to software development conferences, even for me across the Atlantic, to discuss and find new tools.  Much longer ago than I care to remember.

Share this post


Link to post
On 6/12/2021 at 4:03 AM, David Heffernan said:

@David Schwartz Are you familiar with how C# implements out params. Why can we have that? 

no, not that specifically. But I know of a lot of things C# does have that I'd love to see supported in Delphi. 😉

Share this post


Link to post
On 6/12/2021 at 10:13 AM, Anders Melander said:

image.thumb.png.28173de0f53f177f61ff680398041202.png

Yes, exactly! Most of these I was able to solve, although some took quite a lot of head scratching. 

Share this post


Link to post

Currently "out" is more just a usage hint than real compiler-controlled restriction. But even this state is better than "var". Hoping sometimes in Delphi v.25 Tegucigalpa they will add "use uninit'ed" compiler warning.

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×