Types
Tusk features a rich type system,
closely mirroring
that of Delphi.
Tusk defines 22 primitive data types…
- Variant
- Variant
- Any
- NoCall
- Integer
- Int8 / ShortInt
- UInt8 / Byte
- Int16 / SmallInt
- UInt16 / Word
- Integer / Int32
- Cardinal / UInt32
- Int64 / UInt64
- Float
- Double (no support for Extended)
- Single
- Currency
- String / Char
- string / UnicodeString
- AnsiString
- Char
- AnsiChar
- Boolean
- Boolean
- Date / Time
- TDateTime
- Unit
- Units are only used as a prefix, as in DSList.IList.
- Data Type
- IType
Tusk supports Delphi's string and AnsiString types,
but not WideString (use string instead).
As in Delphi, Tusk strings are one-based, not zero-based.
This applies even when accessing a string through an Any
variable…
var s: string := 'hello';
Writeln(s[2]); // prints "e", not "l"
var v: Any := s;
Writeln(v[2]); // prints "e", not "l"
This also applies to standard RTL functions like Copy,
Insert, and Delete…
var s: string := 'hello';
Writeln(Copy(s, 2, 2)); // prints "el", not "ll"
var v: Any := s;
Writeln(Copy(v, 2, 2)); // prints "el", not "ll"
Note that Delphi doesn't allow indexing into a string
value through a Variant variable.
Tusk fully supports Delphi's
enum types, including scoped enums.
The syntax for defining an enum in Tusk is the same as in
Delphi, except that Tusk does not support assigning explicit
ordinal values to enum choices
(this is not a good idea in Delphi,
as it causes the type to not have run-time type information).
Here are some examples of defining enum types…
type
TDrumKind = (dkSnare, dkKick, dkFloorTom, dkRackTom);
{$ScopedEnums On}
TBasicColor = (Black, Gray, White, Red, Green, Blue, Yellow);
{$ScopedEnums Off}
With the above definitions, the following code demonstrates using
enum types…
var DrumKind: TDrumKind := dkFloorTom;
var Color := TBasicColor.Green;
for DrumKind := Low(TDrumKind) to High(TDrumKind) do
Writeln(Ord(DrumKind));
DrumKind := TDrumKind.dkSnare;
Note that Tusk, like Delphi, stores enums as Integer values
when converting an enum to Variant.
For a complete list of enum types exposed to Tusk,
consult the RTL documentation.
Tusk fully supports Delphi's
set types, including sets of Byte, Int8,
AnsiChar, Boolean, and enum types.
The syntax for defining a set in Tusk is the same as in Delphi.
For example…
type
TDrumKind = (dkSnare, dkKick, dkFloorTom, dkRackTom);
TDrumKinds = set of TDrumKind;
Tusk supports the same set operations as Delphi.
In Tusk, like Delphi, sets not Variant-compatible.
However, the Any and NoCall types don't have this limitation.
For a complete list of set types exposed to Tusk,
consult the RTL documentation.
Tusk fully supports Delphi's
dynamic arrays, with the newer TArray<T> notation,
not the obsolete array of T notation.
For example…
type
TDrumKind = (dkSnare, dkKick, dkFloorTom, dkRackTom);
TDrumKindArray = TArray<TDrumKind>;
For a complete list of dynamic array types exposed to Tusk,
consult the RTL documentation.
Tusk supports the + operator
for concatenating dynamic arrays…
var a: TArray<Byte> := [1,2,3];
var b := a + [4,5,6];
Above, b is initialized to [1,2,3,4,5,6].
Tusk also supports Delphi's dynamic array constructor notation…
var x := TArray<Double>.Create(1, 2, 4.5);
In Delphi, when a dynamic array converted to Variant,
it becomes a
Variant array,
which is very different from a Delphi-native dynamic array…
- Variant arrays use the COM memory manager, not Delphi's FastMM memory manager, resulting in noticeable performance degradation.
- Variant arrays are not reference-counted: every Variant array is a brand new copy of the data.
- Variant arrays may only contain OLE-compatible elements (integers, floats, WideString, Boolean, date/times, and interfaces). Specifically, Delphi records and sets are not supported.
Tusk includes pointer types, which are familiar to most
Delphi programmers…
var a, b: Integer;
var p: ^Integer;
p := if Odd(Length(GetUserName)) then @a else @b;
Inc(p^);
Writeln(a);
Writeln(b);
Above, p will point to either a or b
(depending on whether the current user name has
an odd or even number of characters).
Then the program increments the integer p points to.
For a complete list of pointer types exposed to Tusk,
consult the RTL documentation.
In Delphi, pointers are not
memory-safe,
opening programs up to issues such as buffer overflows and
over-writes, dangling pointers, wild pointers, etc.
In Tusk, when you take the address of a variable
(using the @ operator),
the result points to a value that will be kept "alive"
as long as the pointer survives.
For example…
function GetIntPtr: PInteger;
var
x: Integer;
begin
x := 123;
Result := @x;
end;
var p: PInteger := GetIntPtr;
Writeln(p^);
Inc(p^);
Writeln(p^);
The above program prints 123 followed by 124.
In Delphi, this program exhibits a serious problem:
GetIntPtr returns the address of a local variable,
which ceases to exist once the function returns.
This is not a problem for Tusk –
the local variable x continues to exist until
p no longer references it.
Additionally, Tusk includes runtime checks when using pointers.
For example, revisiting the above code, suppose there's a bug…
var p: PInteger := GetIntPtr;
Writeln(p^);
Inc(p); // NOTE: should be Inc(p^), not Inc(p)!
Writeln(p^);
This code is incrementing p, instead of incrementing p^.
In Delphi, this causes the subsequent Writeln to print out
some random/garbage value.
In Tusk, however, the call to Inc(p) is a runtime error:
Tusk knows that p doesn't point to an array or list,
so incrementing it is not allowed.
Like Delphi, Tusk supports pointer math
(addition and subtraction operations involving pointers).
Unlike Delphi, pointer math is memory-safe in Tusk…
type PInteger = ^Integer;
var a: TArray<Integer> := [1,2,3,4,5,6];
var p: PInteger;
p := @a[0];
for var i := 0 to High(a) do begin
Writeln(p^);
Inc(p);
end;
Tusk supports taking the address of a property…
var ls := NewStringList;
var cs := @ls.CaseSensitive;
ls.CaseSensitive := False;
Writeln(cs^); // prints False
ls.CaseSensitive := True;
Writeln(cs^); // prints True
cs^ := False;
Writeln(ls.CaseSensitive); // prints False
As the example above demonstrates, a pointer to a property
is an additional way to access the property's value.
Above, the variable cs maintains a reference to the
IStringList object, independent of the ls variable…
var ls := NewStringList;
var cs := @ls.CaseSensitive;
ls.CaseSensitive := False;
Writeln(cs^); // prints False
ls := NewStringList; // ls and cs diverge
ls.CaseSensitive := True;
Writeln(cs^); // prints False
Above, assigning a new value to ls causes ls and cs
to no longer reference the same IStringList object.
When taking the address of a property in a record,
the pointer maintains a reference to the variable that
holds the record.
Tusk also supports taking the address of elements in
an array property…
var b := NewPropBag;
b['x'] := 'hello';
b['y'] := 'goodbye';
var p := @b['x'];
Writeln(p^); // prints "hello'
p^ := UpperCase(p^);
Writeln(b['x']); // prints "HELLO"
The same idea applies to lists…
var ls := NewList(['hello', 3, True]);
var p := @ls[1];
Writeln(p^); // prints 3
Inc(p^);
Writeln(ls[1]); // prints 4
In the above example, Tusk understands that the IVector.Items
property is array-like, so it allows pointer math on p…
var ls := NewList(['hello', 3, True]);
var p := @ls[0];
Writeln(p^); // prints "hello"
Inc(p);
Writeln(p^); // prints 3
Writeln(p[1]); // prints True
Tusk only supports pointer math on specific array properties
such as IVector.Items,
but not others such as IAssociation.Values.
In the Tusk RTL Documentation,
you'll see such properties annotated with the [TuskArray]
attribute (for example, scroll to the end of
DSGenUtil.IVector).
In Tusk, anonymous method types are just like in Delphi…
type
TLoginFunc = reference to function(
const User, Pwd: string): Boolean;
TWorkProc = reference to procedure(
const Items: TArray<IPropBag>);
The above example defines two types: TLoginFunc is a function
taking two string arguments and returning a Boolean;
TWorkProc is a procedure taking a dynamic array of IPropBag.
These types are compatible with nil,
and with functions / functions with compatible signatures.
For example…
type
TWorkProc = reference to procedure(
const Items: TArray<IPropBag>);
var p: TWorkProc :=
procedure(const A: TArray<IPropBag>)
begin
for var Bag in A do
Writeln(Inspect(Bag));
end;
p([NewPropBag]);
p([]);
p(nil);
For a complete list of anonymous method types exposed to Tusk,
consult the RTL documentation.
Tusk supports records defined in Delphi,
including the following features…
- fields
- methods
- instance methods
- class methods
- record constructors
- operator overloads
- properties
- read-only, write-only, and read/write properties
- array properties
- default array properties
- nested constants
- nested types
Tusk supports interfaces defined in Delphi,
including the following features…
- methods
- properties
- read-only, write-only, and read/write properties
- array properties
- default array properties
- casting with the is and as operators
- casting with TType.Supports and TType.VarSupports
Tusk supports generic interfaces defined in Delphi,
including instantiating a generic interface using
types defined in Tusk.
For example…
type
TSize = (XSmall, Small, Medium, Large, XLarge);
TSizes = set of TSize;
ISizesSet = ISet<TSizes>;
Above, the generic interface ISet<T> is instantiated
on TSizes, which is a type defined at runtime.
For a complete list of generic interface types exposed to Tusk,
consult the RTL documentation.
⏱ Last Modified: 2/15 10:06:11 am