I would like to make a proposal for a small language feature, which could be really useful. I named this feature "Record Composition", but it already crossed my mind that because of the composition pattern in OOP, this may not be the most fitting name.
As I do not know what the offical way to submit such ideas/contributions is (there seems to be no information in the gitlab or the website), so I will just post it here, as I know that many FPC developers are watching this Forum regularly.
The basic idea is simple, just allow access to the subfields of a record element directly through the parent record. Example:
{$ModeSwitch RecordComposition}
type
TChildRec = record
A: Integer;
end;
TComposed = record
uses child: TChildRec;
B: Double;
end;
var
c: TComposed;
begin
c.A := 42;
c.B := 3.14;
WriteLn(c.child.A); // Writes 42
end.
I've already implemented it in the FPC sources, to see how feasable such an implementation would be:
https://gitlab.com/freepascal.org/fpc/source/-/merge_requests/498You can look at the commited test cases to see some more examples.
Why I think this is useful/requiredThere are basically three main reasons for it:
1. Since C11 C allows so called anonymous structures and anonymous unions within their structs. This means that while records have been mostly equivalent with C structs allowing easy porting of C code to Pascal, anonymous records and anonymous unions are not yet compatible, making it challenging to port C code using it. Record composition would basically resolve this problem using anonymous composition fields (already implemented):
struct foo {
int x;
struct bar; // anonymous struct inclusion of struct bar
}
Can now be represented by:
TFoo = record
x: Integer;
uses TBar;
end;
2. Allowing for non trailing and multiple variant parts. As was discussed previously in
https://forum.lazarus.freepascal.org/index.php/topic,63226.0.html there seems to be a need for having the variant part of the record not in the end, or having multiple variant parts within a record. Also this can be solved using record composition:
TFoo = record
x: Integer;
uses record
case Boolean of
True: (I: Integer);
False: (D: Double);
end;
y: Integer;
end;
3. It allows to easier organize record contents throughout multiple definitions. For example if you have platform dependent data, you don't need massively nested {$IfDef}-{$Else}-{$EndIf} directives, you can simply write different types and import them:
TFoo = record
CommonField: Integer;
{$IfDef Windows}
uses TFooWinInternal;
{$Else}
uses TFooUnixInternal;
{$EndIf}
end;
There are also a few minor reasons to do this, especially interesting for advanced records. For example, one of the main issues with advanced records is that they don't support inheritance. While this composition is no replacement for inheritance, as it does not provide virtual methods or similars, the ability to compose a record from some base definition with some additional data and/or functionality is probably fully sufficient in most situations, giving more options without having to resort to a full inheritance model.
Second, one thing I experimented with was to use this to enable records as wrappers for objects and classes, for example one could imagine an "shared pointer" like this:
TSharedSL = record
private
ref: TStringList;
refcount: PInteger;
public
using ref;
class operator Initialize(...); // Will create ref
class operator Finalize(...); // will decrease refcount
class operator Copy(...); // Decrease -> copy -> increase refcount
class operator AddRef(...); // Will increase refcount
end;
var
sl: TSharedSL;
begin
sl.Add('Hello');
sl.Add('World');
WriteLn(sl.Text);
end.
But I'm a bit unsure about that one, while I currently have built it such that classes can also be composited, I think that just including large classes will mostly create irritating types with way to many fields and field collisions everywhere. So I don't think if there is really any use-case for this that makes it worth it.
Lastly its a very simple addition to the FPC, the diff on the compiler code is only like 200 lines of code, with some of it being for features like allowing to use classes and visibility scoping (such that you can like above have ref within a different visibility than the fields that are imported), which may not even be worth to include in the first place. At it's core it just adds link symbols which when resolved generate the AST for accessing the composite member, with basically only 2 contact points within the FPC source base, such that it will only have very limited effect on other FPC functionality and maintenance.