Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why two aliases to "array of string" treated differently?

In Pascal there are two kinds of type declarations:

  • type aliases: type NewName = OldType
  • type creation: type NewType = type OldType

The former is just creating convenient shorthand, like typedef in C. The aliases are compatible one to another and to their original type. The created types are intentionally incompatible and cannot be mixed without explicit and unsafe by definition typecast.

var
  nn: NewName; nt: NewType; ot: OldType;
...
  nn := ot; // should work
  nt := ot; // should break with type safety violation error.

  nt := NewType(ot); // Disabling type safety. Should work even if 
  // it has no sense semantically and types really ARE incompatible.

Those are Pascal basics as i understand them.

Now let's look at one certain type and two its aliases:

  • System.Types.TStringDynArray = array of string;
  • System.TArray<T> = array of T;
    • in particular that means TArray<string> = array of string; by definition.

Now let's take function returning the former type alias and feed its result to the function expecting the latter one:

uses Classes, IOUtils;

 TStringList.Create.AddStrings(
    TDirectory.GetFiles('c:\', '*.dll') );

 TStringList.Create.AddStrings(
     TArray<string>( // this is required by compiler - but why ???
         TDirectory.GetFiles('c:\', '*.dll') ) );

1st snippet would not compile due to types violation. 2nd one happily compiles and works, but is fragile towards future type changes and is redundant.

QC tells that compiler is right and the RTL design is wrong. http://qc.embarcadero.com/wc/qcmain.aspx?d=106246

WHY compiler is right here ? Why those aliases are incompatible ? Even the very manner RTL was designed suggests that they were deemed compatible!

PS. David suggested even simplier example, without using TArray<T>

 type T1 = array of string; T2 = array of string;

 procedure TForm1.FormCreate(Sender: TObject);
  function Generator: T1;
    begin Result := T1.Create('xxx', 'yyy', 'zzz'); end;
  procedure Consumer (const data: T2);
    begin
      with TStringList.Create do 
      try
        AddStrings(data);
        Self.Caption := CommaText;
      finally
        Free;
      end;
    end;
  begin
    Consumer(Generator);
  end;

Same gotcha without explanation...

PPS. There are a number of doc refs now. I want to stress one thing: while this restriction might be indirectly inherited from Pascal Report of 1949, today is 2012 and Delphi used very differently from school labs of half-century ago. I named few BAD effects of keeping this restrictions, and yet did not saw any good one.

Ironic thing, that this restricion may be lifted without breaking rules of Pascal: in Pascal there is no such non-strict beast as Open Arrays and Dynamic Arrays. So let those original fixed arrays be restricted as they wish, but Open Arrays and Dynamic Arrays are not Pascal citizens and are not obliged to be limited by its codebook!

Please, communicate Emba in QC or maybe even here, but if u just pass by without expressing your opinion - nothing would change!

like image 522
Arioch 'The Avatar asked Jun 14 '12 08:06

Arioch 'The


2 Answers

The key to understanding this issue is the Type Compatibility and Identity topic in the language guide. I suggest you have a good read of that topic.

It is also helpful to simplify the example. The inclusion of generics in the example serves mainly to complicate and confuse matters.

program TypeCompatibilityAndIdentity;
{$APPTYPE CONSOLE}

type
  TInteger1 = Integer;
  TInteger2 = Integer;
  TArray1 = array of Integer;
  TArray2 = array of Integer;
  TArray3 = TArray1;

var
  Integer1: TInteger1;
  Integer2: TInteger2;
  Array1: TArray1;
  Array2: TArray2;
  Array3: TArray3;

begin
  Integer1 := Integer2; // no error here
  Array1 := Array2; // E2010 Incompatible types: 'TArray1' and 'TArray2'
  Array1 := Array3; // no error here
end.

From the documentation:

When one type identifier is declared using another type identifier, without qualification, they denote the same type.

This means that TInteger1 and TInteger2 are the same type, and are indeed the same type as Integer.

A little further on in the documentation is this:

Language constructions that function as type names denote a different type each time they occur.

The declarations of TArray1 and TArray2 fall into this category. And that means that these two identifiers denote different types.

Now we need to look at the section discussing compatibility. This gives a set of rules to follow to determine whether or not two types are compatible or assignment compatible. We can in fact shortcut that discussion by referring to another help topic: Structured Types, Array Types and Assignments which states clearly:

Arrays are assignment-compatible only if they are of the same type.

This makes it clear why the assignment Array1 := Array2 results in a compiler error.

Your code looked at passing parameters, but mine focused on assignment. The issues are the same because, as the Calling Procedures and Functions help topic explains:

When calling a routine, remember that:

  • expressions used to pass typed const and value parameters must be assignment-compatible with the corresponding formal parameters.
  • .......
like image 91
David Heffernan Avatar answered Nov 03 '22 00:11

David Heffernan


Delphi is a strongly typed language. That means that identical (in this case I mean their definitions look exactly the same) types are not assignment compatible.

When you write array of <type> you are defining a type and not an alias. As David already said in his comment the two identical types like

type 
  T1 = array of string; 
  T2 = array of string;

are not assignment compatible.

Same goes for

type
  TStringDynArray = array of string;
  TArray<T> = array of string;

Often people forget about the incompatibility of identical types and my guess would be that they did when they introduced IOUtils for example. Theoretically the definition of TStringDynArray should have been changed to TStringDynArray = TArray<string> but I guess that could have raised other problems (not saying bugs with generics...).

like image 20
Stefan Glienke Avatar answered Nov 03 '22 00:11

Stefan Glienke