Mike Torrettinni 198 Posted June 5, 2021 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
Uwe Raabe 2057 Posted June 5, 2021 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. 2 1 Share this post Link to post
Uwe Raabe 2057 Posted June 5, 2021 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. 1 Share this post Link to post
Mike Torrettinni 198 Posted June 5, 2021 (edited) 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 June 5, 2021 by Mike Torrettinni Share this post Link to post
Rollo62 536 Posted June 6, 2021 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
Mike Torrettinni 198 Posted June 6, 2021 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
Pat Foley 51 Posted June 6, 2021 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. 2 Share this post Link to post
Rollo62 536 Posted June 6, 2021 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. 1 Share this post Link to post
Dalija Prasnikar 1396 Posted June 6, 2021 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. 3 1 Share this post Link to post
David Heffernan 2345 Posted June 7, 2021 Out params in Delphi kinda suck because the compiler does nothing to enforce out semantics. Compare and contrast with C#. 4 Share this post Link to post
Mike Torrettinni 198 Posted June 10, 2021 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
David Heffernan 2345 Posted June 10, 2021 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. 2 Share this post Link to post
Mike Torrettinni 198 Posted June 10, 2021 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
Stefan Glienke 2004 Posted June 10, 2021 Out parameters are just bad var parameters - just saying. 7 Share this post Link to post
Tommi Prami 130 Posted June 11, 2021 Very glad to everyone that brought this in my attention. Did not know all this. Now just have to remember this. -Tee- 1 Share this post Link to post
David Schwartz 426 Posted June 12, 2021 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?" 1 Share this post Link to post
David Heffernan 2345 Posted June 12, 2021 @David Schwartz Are you familiar with how C# implements out params. Why can we have that? Share this post Link to post
Anders Melander 1784 Posted June 12, 2021 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?" 1 Share this post Link to post
timfrost 78 Posted June 13, 2021 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
David Schwartz 426 Posted June 18, 2021 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
David Schwartz 426 Posted June 18, 2021 On 6/12/2021 at 10:13 AM, Anders Melander said: 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
Fr0sT.Brutal 900 Posted June 28, 2021 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. 1 Share this post Link to post