12
votes

It looks like the Delphi compiler does not honor const record parameters when "records-with-methods" are involved.

Having not tried to abuse the const convention previously, I was a little surprised to find the compiler accepted code like that:

type
    TTest = record
       Field : String;
       procedure Update;
    end;

procedure TTest.Update;
begin
    Field := Field + '+1';
end;

procedure DoStuff(const t : TTest);
begin
    ShowMessage(t.Field);
    t.Update;
    ShowMessage(t.Field);
end;

While if you try to do a t.Field:='doh'; in DoStuff f.i., the compiler will properly complain, but you're allowed to call methods that modify the "const" record without even a hint or warning. So this is different behavior than for reference types (such as classes or dynamic arrays), where direct field writes are allowed (as const only restricts changes to the parameter itself).

Addendum: this allows to modify declared compile-time constants this way too, as in:

const
   cTest : TTest = (Field : '1');
...
cTest.Update;              // will show '1' then '1'+'1'
ShowMessage(cTest.Field);  // will show '1' (because optimized at compile-time)

Is that an accepted/documented behavior? or just a compiler shortcoming?

2
I updated the addendum to call the method Update directly since it seems to me that is the fundamental issue here. You need a method of the record to mutate a const record.David Heffernan
IMHO the real bug here is Update. Value types should be used as immutable. (Link is C# example, sorry, but the idea is the same.) See also this SO answer.Craig Stuntz
@David, your edit to the addendum invalidates the example. Directly calling Update modifies the field value, but we don't get to see the result of that change because the compiler apparently optimizes the direct access to it in the subsequent ShowMessage statement.Rob Kennedy
@Rob My edit was made before the point about ShowMessage and optimisation was made. At the time I made the edit, I believe it was reasonable.David Heffernan
The DoStuff was in the sample on purpose before the edit, to show that in practice it can happen in situations where it's non obvious.Eric Grange

2 Answers

15
votes

const never places any restrictions on method calls in Delphi, be they on records or instances of classes. So I don't think there is anything inconsistent with the treatment of method calls.

If methods could not be called on record passed as a const parameter, then that would pretty much render records with methods useless. It would mean, for example, that a property getter could not be called. In order to place restrictions on such records passed as const, there would need to be an equivalent concept to the const member functions of C++. That would allow the compiler to know that certain methods were non-mutating.

4
votes

David analyzed the restriction pretty well. If the compiler was to check out such details it could really do it with some penalty. Additionally I don't see anything wrong with the compiler's behaviour. The method which gets the record can't directly alter its data, but only when using the method it contains. The record in this case works like an object: you can in the same way an object as a const and still have the same problem you described, ie. the object's methods can be used to alter its data.

The benefit of the object is, that such methods can be declared to be private, which enables you to protect its data. You could even create an inherited class which does just that, namely hiding all possibility to alter its data. Maybe you want to try this approach?