|
|
(12 intermediate revisions by 2 users not shown) |
Line 1: |
Line 1: |
− | == Introduction ==
| |
| | | |
− | Delphi and Pascal come from another language family than D. D uses the C syntax. In short, a C syntax uses more symbols and more operators.
| |
− |
| |
− | The following document develops a few very specific points over which D and Delphi/Pascal have some similarities or ambiguities.
| |
− |
| |
− | Note that it's not an exhaustive comparison: for simple things such as loops, conditional statments, control flow, it's advisable to learn a bit the basis from the examples, the [http://dlang.org/spec.html manual], [http://rosettacode.org/wiki/Category:Programming_Tasks Rosetta code], etc.
| |
− |
| |
− | == Types equivalence ==
| |
− |
| |
− | {| border="1" cellpadding="6" cellspacing="0"
| |
− | | '''Pascal'''
| |
− | | '''D'''
| |
− | | '''notes'''
| |
− | |-
| |
− | | Pointer
| |
− | | void*
| |
− | |
| |
− | |-
| |
− | | Byte
| |
− | | ubyte
| |
− | | 8 bit unsigned integer. note the ambiguity when coming from Pascal
| |
− | |-
| |
− | | ShortInt
| |
− | | byte
| |
− | | 8 bit signed integer
| |
− | |-
| |
− | | Word
| |
− | | ushort
| |
− | | 16 bit unsigned integer
| |
− | |-
| |
− | | SmallInt
| |
− | | short
| |
− | | 16 bit signed integer
| |
− | |-
| |
− | | DWord / Cardinal
| |
− | | uint
| |
− | | 32 bit unsigned integer
| |
− | |-
| |
− | | Integer
| |
− | | int
| |
− | | 32 bit signed integer
| |
− | |-
| |
− | | UInt64
| |
− | | ulong
| |
− | | 64 bit unsigned integer
| |
− | |-
| |
− | | Int64
| |
− | | long
| |
− | | 64 bit signed integer
| |
− | |-
| |
− | | Single
| |
− | | float
| |
− | | IEEE 754 32 bit floating point
| |
− | |-
| |
− | | Double
| |
− | | double
| |
− | | IEEE 754 64 bit floating point
| |
− | |-
| |
− | | NativeUInt
| |
− | | size_t
| |
− | | library type, either an alias to a unsigned 32 bit integer or to a unsigned 64 bit integer
| |
− | |-
| |
− | | NativeInt
| |
− | | ptrdiff_t
| |
− | | library type, either an alias to a signed 32 bit integer or to a signed 64 bit integer
| |
− | |}
| |
− |
| |
− | == The object model ==
| |
− |
| |
− | The object model is striclty equivalent. Multiple inheritence of classes is not allowed but can be achieved with interfaces.
| |
− |
| |
− | * Delphi, Pascal
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | type ISome = interface
| |
− | procedure blah();
| |
− | end;
| |
− |
| |
− | type TThing = class
| |
− | end;
| |
− |
| |
− | type TSomething = class(TThing, ISome)
| |
− | procedure blah();
| |
− | end;
| |
− | </syntaxhighlight>
| |
− |
| |
− | * The D2 way:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | interface ISome(){
| |
− | void blah();
| |
− | }
| |
− |
| |
− | class TThing{
| |
− | }
| |
− |
| |
− | class TSomething: TThing, ISome{
| |
− | void blah(){}
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | However some slight difference exists in this model. The Pascal '''virtual''' method attribute does not exist in D. In Pascal, virtual methods must be marked, in D, every method is virtual unless it's marked as '''final'''.
| |
− |
| |
− | for example, in Pascal:
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | type TFoo = class(TObject)
| |
− | private
| |
− | procedure something1(); virtual;
| |
− | procedure something2();
| |
− | end;
| |
− | </syntaxhighlight>
| |
− |
| |
− | and the D equivalent:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | class Foo : Object {
| |
− | private:
| |
− | void something1();
| |
− | final void something2();
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | == Memory management ==
| |
− |
| |
− | Memory management is one of the biggest difference encountered when learning D with a Pascal background. This is less an aspect of the language than from the runtime. D uses a garbage collector (GC) while Pascal and traditionnal Delphi (before the switch to llvm ARC) uses manual memory management.
| |
− |
| |
− | For example the following program in Pascal leaks a TObject.
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | program program1;
| |
− | var
| |
− | obj: TObject;
| |
− | begin
| |
− | obj := TObject.Create;
| |
− | // obj.Free; // leak
| |
− | end;
| |
− | </syntaxhighlight>
| |
− |
| |
− | While the D equivalent does not, because the Object reference is collected:
| |
− | <syntaxhighlight lang="D">
| |
− | void main(string[] args){
| |
− | Object obj = new Object; // new causes collection
| |
− | // the GC will delete Obj automatically
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | However several technics exist to override the GC. For example it's possible to allocate a heap chunk which is not known by the GC, in the same fashion as done in Pascal with '''GetMem''', '''ReallocMem''' and '''FreeMem''':
| |
− | <syntaxhighlight lang="D">
| |
− | import std.c.stdlib;
| |
− | auto chunk = malloc(4096);
| |
− | scope(exit) free(chunk);
| |
− | // some code...
| |
− | </syntaxhighlight>
| |
− |
| |
− | which is equivalent to
| |
− | <syntaxhighlight lang="Pascal">
| |
− | var
| |
− | chunk: Pointer;
| |
− | begin
| |
− | chunk := GetMem(4096);
| |
− | try
| |
− | // some code...
| |
− | finally
| |
− | FreeMem(chunk);
| |
− | end;
| |
− | end;
| |
− | </syntaxhighlight>
| |
− |
| |
− | Allocating an Object instance or a struct in a ''heap'' zone which is not know by the GC is also possible.
| |
− | A few years back, it was possible to write a custom class allocator/deallocator but it's now deprecated.
| |
− | The new way:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | // GC-free struct: auto s = construct!S(a,b,c,...);
| |
− | ST * construct(ST, A...)(A a)
| |
− | if(is(ST==struct))
| |
− | {
| |
− | import std.conv : emplace;
| |
− | auto size = ST.sizeof;
| |
− | auto memory = malloc(size)[0 .. size];
| |
− | if(!memory) throw new Exception("Out of memory");
| |
− | return emplace!(ST, A)(memory, a);
| |
− | }
| |
− |
| |
− | // GC-free class instance: auto c = construct!C(a,b,c,...);
| |
− | static CT construct(CT, A...)(A a)
| |
− | if (is(CT == class))
| |
− | {
| |
− | import std.conv : emplace;
| |
− | auto size = __traits(classInstanceSize, CT);
| |
− | auto memory = malloc(size)[0 .. size];
| |
− | if(!memory) throw new Exception("Out of memory");
| |
− | return emplace!(CT, A)(memory, a);
| |
− | }
| |
− |
| |
− | // destroy a GC-free class: CTEFI able, c.destruct;
| |
− | static void destruct(CT)(ref CT instance)
| |
− | if (is(CT == class))
| |
− | {
| |
− | if (!instance) return;
| |
− | destroy(instance);
| |
− | free(cast(void*)instance);
| |
− | instance = null;
| |
− | }
| |
− |
| |
− | // non-GC structs can be destroyed with std.c.stdlib.free()
| |
− | </syntaxhighlight>
| |
− |
| |
− | You may have the idea to encapsulate the class allocator in a static class function, to emulate the Pascal syntax
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | myInstance = TSomething.create;
| |
− | </syntaxhighlight>
| |
− |
| |
− | For example, using our previous function:
| |
− | <syntaxhighlight lang="D">
| |
− | class Something
| |
− | {
| |
− | static typeof(this) Create(A...)(A a){
| |
− | return construct!(typeof(this))(a);
| |
− | }
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | But this doesn't work because the static method is not redefined in the descendant.
| |
− | For example:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | class SomethingElse: Something {}
| |
− | SomethingElse se = SomethingElse.Create; // wont compile, Create returns a Something not a SomethingElse
| |
− | </syntaxhighlight>
| |
− |
| |
− | == Templates ==
| |
− |
| |
− | Traditional Delphi and Pascal didn’t supported templates. Delphi supports them since D2009, using the most common syntax: the left/right angle brackets (declaration and instantiation of a template).
| |
− |
| |
− | D uses parens for the declaration and the exclamation mark ('''!''') for the instantiation. Parens are used after the '''!''' if the template expects several parameters.
| |
− |
| |
− | * Delphi or FPC with the dialect ''{$Mode Delphi}''
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | type foo<T,G,A> = class
| |
− | end;
| |
− |
| |
− | type fooccc = class(foo<char, char, char>)
| |
− | end;
| |
− |
| |
− | var
| |
− | bar: foo<integer,single,double>;
| |
− | </syntaxhighlight>
| |
− |
| |
− | * FPC with the dialect ''{$Mode objfpc}'' (aka '''''object''''' Pascal)
| |
− |
| |
− | Faithfully to the Pascal tradition, the objfpc dialect recquires two explicit keywords for the template declaration and the template instantiation: '''generic''' and '''specialize''':
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | type generic foo<T,G,A> = class
| |
− | end;
| |
− |
| |
− | type fooccc = class(specialize foo<char, char, char>)
| |
− | end;
| |
− |
| |
− | var
| |
− | bar: specialize foo<integer,single,double>;
| |
− | </syntaxhighlight>
| |
− |
| |
− | * the D2 way:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | class foo(T,G,A){
| |
− | }
| |
− |
| |
− | class fooccc: foo!(char, char, char){
| |
− | }
| |
− |
| |
− | foo!(int, float, double) bar;
| |
− | </syntaxhighlight>
| |
− |
| |
− | To go further with the D2 templates, it's important to note that the syntax used in the previous example matches to what is called
| |
− | an '''eponymous template'''. An eponymous template is a template which contains only one member using the same identifier.
| |
− |
| |
− | for example, the previous class foo is simplified form of:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | template foo(T,G,A){
| |
− | class foo{}
| |
− | }
| |
− | // <template_name>!(params...).template_member
| |
− | class fooccc: foo!(char, char, char).foo {}
| |
− | </syntaxhighlight>
| |
− |
| |
− | This reveals that D templates are more powerfull than their Pascal equivalent because almost everything (alias, function, class, struct, interface, etc.) can be enclosed by a definition of parameters:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | template tmp(T,C){
| |
− |
| |
− | void functA(){/*can use the parameters A and C*/}
| |
− |
| |
− | C functA(){/*can use the parameters A and C*/ return C.init;}
| |
− |
| |
− | T functA(){/*can use the parameters A and C*/ return T.init;}
| |
− |
| |
− | interface intfA{
| |
− | void methodA(T aT);
| |
− | void methodB(C aC);
| |
− | }
| |
− |
| |
− | alias param1_t = T;
| |
− | alias param2_t = C;
| |
− |
| |
− | // etc.
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | == Conditional compilation ==
| |
− |
| |
− | D conditional compilation is one of the unique feature of the lanquage. While in Pascal you used a custom definition like this:
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | a := a shr 3;
| |
− | {$IFDEF MYSWITCH}
| |
− | // some conditionally compiled code
| |
− | {$ENDIF}
| |
− | </syntaxhighlight>
| |
− | in D the equivalent use a standard language construct '''''version(Identifier)''''':
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | a =>> 3;
| |
− | version(MYSWITCH){
| |
− | // some conditionally compiled code
| |
− | }
| |
− | </syntaxhighlight>
| |
− | But now you’ll get more. A feature that doesn’t exist in Pascal and Delphi is the '''''static if''''' expression. It allows a more advanced conditional compilation, particularly in the templates, since the type of the template parameters can be tested when it gets instantiated.
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | struct foo(T) {
| |
− | // something is only compiled if T type is string.
| |
− | static if (is(T == string)){
| |
− | void something(){}
| |
− | }
| |
− | }
| |
− | </syntaxhighlight>
| |
− | This is also related to the template constraints (the constraint is applied to the whole template while in our previous example it’s only applied to the enclosed expressions). You might know the principle if you’ve used Delphi XE6 or upper.
| |
− |
| |
− | == Inclusion ==
| |
− |
| |
− | D has a feature similar to Pascal/delphi source inclusion.
| |
− |
| |
− | for the example the following inclusion:
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | procedure Something;
| |
− | begin
| |
− | {$IFDEF DEMO}
| |
− | {$I myDemoImplementation.inc}
| |
− | {$ELSE}
| |
− | {$I myFullImplementation.inc}
| |
− | {$ENDIF}
| |
− | end;
| |
− | </syntaxhighlight>
| |
− |
| |
− | is rewritten in D
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | void something()
| |
− | {
| |
− | version(DEMO)
| |
− | mixin(import(myDemoImplementation.imp));
| |
− | else
| |
− | mixin(import(myFullImplementation.imp));
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | In both cases, the file content doesn't represent a full unit/module but only some raw code which has to be compilable in the inclusion/mixin context.
| |
− |
| |
− | == RTTI, published attribute and properties. ==
| |
− |
| |
− | In Pascal and Delphi, the Runtime Type Informations (RTTI) are an important feature used by the TPersistent and the TComponent system, as well as for custom serialization or for software settings or even Object dumping into a database.
| |
− |
| |
− | D has no equivalent feature. The '''published''' attribute does not exist but instead you have some compile-time reflection (traits, std.traits, user defined attributes) which could be used to design a similar system.
| |
− |
| |
− | Pascal properties are deeply linked to the RTTI mechanisms. D properties are a bit different and, in a way, more powerfull. They allow to use the assign operator instead of calling the setter with parens (so far this is a common behaviour) but they also can be '''overloaded'''.
| |
− |
| |
− | A classic, non published, Pascal property (interface section only):
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | private
| |
− | FField: integer;
| |
− | procedure SetField(AValue: Integer);
| |
− | public
| |
− | property Field: integer read FField write SetField;
| |
− | </syntaxhighlight>
| |
− |
| |
− | The D equivalent, with overload:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | private:
| |
− | int fField;
| |
− | public:
| |
− | @property void field(int aValue){fField = aValue;}
| |
− | @property void field(string aValue){fField = to!int(aValue); }
| |
− | @property int field(){return fField;}
| |
− | </syntaxhighlight>
| |
− |
| |
− | == Sets and the "in" operator ==
| |
− |
| |
− | In Pascal and Delphi you used the '''in''' operator for testing the presence of a value in a set. D also have this operator but its usage is different, it’s used to test the presence of a value in an associative array (AA):
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | auto asar = [
| |
− | 8 : "AA",
| |
− | 15 : "GJ",
| |
− | 32 : "VJ"
| |
− | ];
| |
− | assert(8 in asar);
| |
− | assert(!(57 in asar));
| |
− | </syntaxhighlight>
| |
− |
| |
− | The Pascal syntax used to define a set does not exist in D, for example the following expression has no simple equivalence:
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | const THexChars Set Of Char = ['0'..'9','a'..'f','A'..'F'];
| |
− |
| |
− | function IsHexChar(C: Char): Boolean;
| |
− | begin
| |
− | result := C in THexDigits;
| |
− | end;
| |
− | </syntaxhighlight>
| |
− | Additionally to the '''in''' difference, double dots '''..''' (slice operator) are not as flexible as in Pascal. The left hand side and the right hand side must be some integral indexes.
| |
− |
| |
− | However, as D supports operator overloading, it’s possible to extend the usage of the '''in''' operator and the slice operator in a custom type. for example:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | import std.stdio;
| |
− | import std.traits;
| |
− |
| |
− | struct bitWise(T) if (isIntegral!T){
| |
− | T theValue;
| |
− | alias theValue this;
| |
− | bool opIn_r(T aValue){
| |
− | return ((aValue & theValue) >= aValue);
| |
− | }
| |
− | }
| |
− |
| |
− | void main(string args[]){
| |
− | bitWise!ubyte bw;
| |
− | bw = 0b11110000;
| |
− | assert( 0b11100000 in bw );
| |
− | assert( !(0b00001111 in bw));
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | Even better, a complete emulation of the THexChars from the previous Pascal example.
| |
− | The '''in''' operator is still overloaded but this time also the '''slice''' one, using two characters as arguments:
| |
− | <syntaxhighlight lang="D">
| |
− | CharSet charSet;
| |
− | struct CharSet
| |
− | {
| |
− | private string str;
| |
− | public typeof(this) opSlice(size_t N)(char lo, char hi){
| |
− | typeof(return) result;
| |
− | foreach(c; lo .. hi+1)
| |
− | result.str ~= c;
| |
− | return result;
| |
− | }
| |
− | public typeof(this) opIndex(Slices...)(Slices slices){
| |
− | typeof(return) result;
| |
− | foreach(slice; slices)
| |
− | result.str ~= slice.str;
| |
− | return result;
| |
− | }
| |
− | public bool opIn_r(char elem){
| |
− | import std.algorithm;
| |
− | return canFind(str,elem);
| |
− | }
| |
− | }
| |
− |
| |
− | void main(string args[]){
| |
− | auto hexChars = charSet['0'..'9', 'A'..'F', 'a'..'f'];
| |
− | assert('D' in hexChars);
| |
− | assert(!('G' in hexChars));
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | == The "is" operator ==
| |
− |
| |
− | The keyword '''is''' exists in the two languages but have a different meaning.
| |
− |
| |
− | In Pascal, '''is''' is used to test if a class inherits from a particular ancestor type. For example
| |
− | <syntaxhighlight lang="Pascal">
| |
− | procedure TMyClass.Event(Sender: TObject);
| |
− | begin
| |
− | if (Sender is TButton) then with TButton(Sender) do begin
| |
− | // some code related to Sender as TButton...
| |
− | end;
| |
− | if (Sender is TCheckBox) then with TCheckBox(Sender) do begin
| |
− | // some code related to Sender as TCheckBox...
| |
− | end;
| |
− | end;
| |
− | </syntaxhighlight>
| |
− |
| |
− | In D, the same can be achieved by comparing the result of a cast to '''null''':
| |
− | <syntaxhighlight lang="D">
| |
− | void event(Object sender){
| |
− | Button button = cast(Button) sender;
| |
− | Checkbox checkbox = cast(Checkbox) sender;
| |
− | if(button){/*some code related to sender as Button*/}
| |
− | if(checkbox){/*some code related to sender as Checkbox */}
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | In D, '''is''', a common usage of the [http://dlang.org/expression#IsExpression is operator] is to test the equivalence of two types, at compile-time. It's often used in the template constraints, for example:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | template myTemplate(T) if ((is(T == float)) | (is(T == double)))
| |
− | {
| |
− | T functionA(T param0, T param1){return T.init;}
| |
− | T functionB(T param0, T param1){return T.init;}
| |
− | }
| |
− | // compile time error, int does not verify the constraint ((is(T == float)) | (is(T == double)))
| |
− | auto value = (myTemplate!int).functionA(1,2);
| |
− | </syntaxhighlight>
| |
− |
| |
− | == Unit "Initialization" and "Finalization" ==
| |
− |
| |
− | D has a similar feature to Pascal unit '''Initialization''' and unit '''Finalization''' sections. They are called static module contructors and static module destructors. Additionally to the feature similarity, there can be many of them and are executed in lexical order.
| |
− |
| |
− | * the Pascal way:
| |
− | <syntaxhighlight lang="Pascal">
| |
− | unit unit1;
| |
− | interface
| |
− | var
| |
− | Obj: TObject;
| |
− | implementation
| |
− | initialization
| |
− | Obj := TObject.Create;
| |
− | finalization
| |
− | Obj.Free;
| |
− | end.
| |
− | </syntaxhighlight>
| |
− |
| |
− | * the D2 way:
| |
− | <syntaxhighlight lang="D">
| |
− | module module1;
| |
− | Object obj;
| |
− | static this(){
| |
− | obj = new Object;
| |
− | }
| |
− | static ~this(){
| |
− | assert(!obj); // executed at last.
| |
− | }
| |
− | static ~this(){
| |
− | delete obj;
| |
− | }
| |
− | static ~this(){
| |
− | assert(obj); // executed at first.
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | == Type aliasing ==
| |
− |
| |
− | Pascal has two syntaxes for aliasing a type:
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | type MyInt = Integer; // syntax 1
| |
− | type MyOtherInt = type Integer; // syntax 2
| |
− | </syntaxhighlight>
| |
− |
| |
− | The first syntax, which in both cases doesn't create a distinct type, is the equivalent of the following D2 declaration:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | alias myInt = int;
| |
− | </syntaxhighlight>
| |
− |
| |
− | The second syntax creates a distinct type (you may, for example, used it to create a custom property inspector).
| |
− | The D2 way is to use a '''struct''' combined with an '''alias this''' expression:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | struct MyOtherInt {
| |
− | public int daValue;
| |
− | alias daValue this;
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | In this basic form, a '''MyOtherInt''' can be used as an '''int''' but is a distinct type. If this syntax looks a bit heavy for the few it does this is because
| |
− | it becomes more interesting when such a struct includes some operator overloading, like in this trivial example:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | struct ExoticFloat {
| |
− | public float daValue;
| |
− | alias daValue this;
| |
− | void opAssign(string aString){
| |
− | import std.conv;
| |
− | daValue = to!float(aString);
| |
− | }
| |
− | }
| |
− | ExoticFloat ef;
| |
− | ef = "0.1";
| |
− | assert(ef == 0.1f);
| |
− | </syntaxhighlight>
| |
− |
| |
− | == Top index in loops and ranges ==
| |
− |
| |
− | Pascal '''for''' loops syntax is quite different from their C-style equivalent,
| |
− | which includes at test and whose index variable is declared inline:
| |
− |
| |
− | <syntaxhighlight lang = Pascal>
| |
− | for i := 0 to high(array) do (*statement*);
| |
− | for i := high(array) downto 0 do (*statement*);
| |
− | </syntaxhighlight >
| |
− |
| |
− | is in D equivalent to:
| |
− |
| |
− | <syntaxhighlight lang = D>
| |
− | for (auto i = 0; i < array.length; i++) /*statement*/;
| |
− | for (ptrdiff_t i = array.length - 1; i > -1; i--) /*statement*/;
| |
− | </syntaxhighlight >
| |
− |
| |
− | But the most important difference is that the top index is not included, which explains why in the reversed form, the loop index type is signed. Type inference would lead '''i''' type to be '''size_t''' (because the array property '''length''' is '''size_t'''), thus always positive and the loop wouldn't start at all.
| |
− |
| |
− | Another syntax exists, which is less error-prone and somehow near from the Pascal '''for''' loops.
| |
− | The trick consists in iterating trough a range, constant or based on run-time variables.
| |
− | Still using the two previous examples:
| |
− |
| |
− | <syntaxhighlight lang = D>
| |
− | foreach (i; 0 .. array.length) /*statement*/;
| |
− | foreach_reverse (i; 0 .. array.length) /*statement*/;
| |
− | </syntaxhighlight >
| |
− |
| |
− | Here again, the last element of the range is not included but the reversed form will not lead to an hidden error.
| |
− |
| |
− | == The const-ness gate ==
| |
− |
| |
− | When coming from Pascal or Delphi, the '''const''' storage class for parameters makes sense for simple values, for example:
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | procedure something(const parameter: Integer);
| |
− | var
| |
− | local_copy: Integer;
| |
− | begin
| |
− | //inc(parameter); // ouch, parameter is const
| |
− | local_copy := parameter;
| |
− | inc(localcopy);
| |
− | end;
| |
− | </syntaxhighlight>
| |
− |
| |
− | In D the same is true:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | void something(const int parameter);{
| |
− | //parameter++; // ouch, parameter is const
| |
− | int local_copy = parameter;
| |
− | localcopy++;
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | However, despite of the appearances, storage classes for parameters are radically different in Pascal and Delphi.
| |
− | In a way we could say that they are '''outrageously permissive''', for example if you use '''const'''
| |
− | for a reference type, let's say a class instance:
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | program Project1;
| |
− |
| |
− | type TFoo = class
| |
− | member: NativeInt;
| |
− | end;
| |
− |
| |
− | procedure FakeConst(const aFoo: TFoo);
| |
− | begin
| |
− | aFoo.member := 1;
| |
− | end;
| |
− |
| |
− | var foo: TFoo;
| |
− |
| |
− | begin
| |
− | foo := TFoo.Create;
| |
− | FakeConst(foo);
| |
− | end.
| |
− | </syntaxhighlight>
| |
− |
| |
− | Compiles fines, despite of the '''aFoo''' const-ness, while in D2,
| |
− | the equivalent:
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | module program;
| |
− |
| |
− | class Foo{
| |
− | public size_t member;
| |
− | }
| |
− |
| |
− | void RealConst(const Foo aFoo){
| |
− | aFoo.member = 1;
| |
− | }
| |
− |
| |
− | void main(string args[]){
| |
− | auto foo = new Foo;
| |
− | RealConst(foo);
| |
− | }
| |
− | </syntaxhighlight>
| |
− |
| |
− | leads to an error:
| |
− |
| |
− | <code>Error: cannot modify const expression aFoo.member</code>
| |
− |
| |
− | because you try to modify a const parameter. D2 is more strict in regard of the '''const''' parameter storage class.
| |
− |
| |
− | You'll encounter the same '''strict''' behavior with modular attributes (@safe, @trusted, @system, @nogc).
| |
− |
| |
− | == Lists ==
| |
− |
| |
− | In Delphi or Pascal, the '''classes''' unit provides the basic lists. In the standard D library, there is no equivalent.
| |
− | Why ? Because the way you used to use a list in Pascal is not efficient, for example the array syntax, classic usage of a list:
| |
− |
| |
− | <syntaxhighlight lang="Pascal">
| |
− | for i := 0 to list.count-1 do
| |
− | TMySubClass(list[i]).stuff := value;
| |
− | </syntaxhighlight>
| |
− |
| |
− | have two major problems:
| |
− |
| |
− | ''list.count'': a basic singly-linked-list system requires to go from first to last, so the count requires a costly traversal:
| |
− | <syntaxhighlight lang="Pascal">
| |
− | while (current <> nil) do begin
| |
− | current := current.next;
| |
− | inc(result);
| |
− | end;
| |
− | </syntaxhighlight>
| |
− |
| |
− | ''list[i]'': for each item, an iterator starts from the beginning.
| |
− | <syntaxhighlight lang="Pascal">
| |
− | while (index <> i) do begin
| |
− | current := current.next;
| |
− | inc(index);
| |
− | result := current.data;
| |
− | end;
| |
− | </syntaxhighlight>
| |
− |
| |
− | So the procedure have a very bad complexity: '''O log N''' with N the '''list.count''' value.
| |
− |
| |
− | The lists in the standard '''D''' library just give you the opportunity to iterate.
| |
− | If you want the Pascal semantic (add, remove, []...) you must wrap a list in a class, for example a '''Dlist'''.
| |
− |
| |
− | '''Std''' lists are structs, so encapsulating one in a class is not a problem, their fields are not accessed by reference.
| |
− | The operations you used in Delphi lists are available in '''std.range''' and '''std.algorithm'''.
| |
− |
| |
− | Before using these modules it's necessary to return a range from the container (if you don't then you do the most common error in D2).
| |
− |
| |
− | <syntaxhighlight lang="D">
| |
− | struct Foo{}
| |
− | DList!(Foo*) lst;
| |
− | foreach(i; 0 .. 10)
| |
− | lst.insertBack(new Foo);
| |
− | auto usableRange = lst[];
| |
− | </syntaxhighlight>
| |
− |
| |
− | It's also worth noting that most D code simply uses normal arrays rather than lists, and appends '''~''' elements directly to them.
| |