Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TGenericStructList readonly record elements? #63

Closed
eugeneloza opened this issue Apr 9, 2017 · 2 comments
Closed

TGenericStructList readonly record elements? #63

eugeneloza opened this issue Apr 9, 2017 · 2 comments

Comments

@eugeneloza
Copy link
Member

I'm getting a "The argument cannot be assigned to" error when trying to change TGenericStructList elements fields. Is this correct? Works fine with object lists... Didn't try it with simple lists (like integer lists).

type TSomeRecord = record
  value: integer;
end;
type TMyList = specialize TGenericStructList<TSomeRecord>;
...
var List: TMyList;
    MyRec: TSomeRecord;
...
List := TMyList.create;
MyRec.value := 0;
List.add(myRec);
List[0].value := 1; //<--------- "Error: The argument cannot be assigned to"
@michaliskambi
Copy link
Member

This is correct, and it's a good thing that the compiler warns about this. In some old compiler versions, this was allowed and resulted in nasty bugs.

Why?

The property that implements the [] access to the list is done by a getter/setter function. More precisely, the implementation of TGenericStructList contains (simplifying) something like this:

  generic TGenericStructList<T> = class(TFPSList)
  private
    function  Get(Index: Integer): T;
    procedure Put(Index: Integer; const Item: T);
  public
    property Items[Index: Integer]: T read Get write Put; default;

What this means is that

List[I].Value := 1;

is equivalent to

List.Get(I).Value := 1;

Now, the Get returns a record. You can imagine that it's implementation looks like this:

function TGenericStructList.Get(Index: Integer): T;
begin
  Result := FSomeInternalList[Index];
end;

Which means that Get copies the record contents (from FSomeInternalList[Index] to Result). And List.Get(I).Value := 1; changes the "Value" field of a record returned by the Get method, which is a temporary record. It does not change the "Value" field of the record stored in a list (FSomeInternalList[Index]). To do it correctly, you must do:

Rec := List[I];
Rec.Value := 1;
List[I] := Rec;

In contrast, List[I].Field := something works with a list of class instances, because then the equivalent Get method returns a reference to the instance.

It's the same in other programming languages. E.g. in C# they have "structs" (much like a "record" in Pascal) and something similar to Pascal properties, and they also disable doing List[I].Value = 1 in it's a list of structs. See http://stackoverflow.com/questions/51526/changing-the-value-of-an-element-in-a-list-of-structs

So it's not something easily "fixable" at the language level, Pascal is not alone here. If you allow records/structs (that are automatically copied at assignment) then you have this problem.

This is also why we have List and L properties on the TGenericStructList. So you can do List.List^[I].Value := 1 and it will work OK. Or List.L[I].Value := 1. I have improved their documentation now (see the commit referenced in this issue), to explain this:)

@eugeneloza
Copy link
Member Author

Thanks a lot! I should have checked that.... :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants