[name] allows for user definition of custom data types. They are typically a set of attributes (variables internal to the data type and accessible from the outside with a system of visibility rings), and a set of methods (functions exclusive to the data type).
We can give a "name" to an unknown data type, just like we can call the variable "x" in equations and such. This allows us to have a data type which is variable, but consistent over the whole context of custom type, function or even a type's methods. In a type, the generics have to be specified in order for the actual memory structure of the type to adapt. In functions, the generics can be inferred automatically from the arguments (a function with only a generic as return type may not exist, except in the context of constructors/destructors), and a new "version" of the function is generated by the compiler to work with the required type. Only types that are actually used with a function have a version of the function generated for them.
In [name], type definition is done as such:
type pragmasoptname <generics>opt (attributes)
type pragmas opt name <generics>opt (attributes) : source
Or to inherit properties from an existing custom data type:
type pragmasopt name <generics>opt (attributes) : source
This syntax can also be used to create aliases of other data types, in a similar fashion to C's typedef (note: for normal inheritance, only another custom data type's name is allowed):
type name : source
The syntax for methods is explained in the Functions Page.
Attributes of custom types can make use of the special keywords priv and rdonly to change their visibility.
type Vector2 (f64x, f64y);
Defining a type, Vector2, with attributes f64 x and f64 y
type Vector3 (f64 z) : Vector2;
Defining a type, Vector3, inheriting Vector2 and adding attribute f64 z
type LinkedList (T val, priv LinkedList* prev, priv LinkedList* next);
Defining a type, LinkedList, which uses generic T for the user to be able to choose the type of the value.
type CString : u8*
Creating an alias, CString, for type u8*
The default behaviour of custom data types is object-like: the developer should not have to care about its internal structure, as order and alignment are optimized by the compiler. Also, custom data types have a base structure common to all objects containing a virtual dispatch table (containing the `u64 Hash()` function), an identifier unique to the type (for use with `instanceof`), both unaccessible as normal attributes; Additional attributes or methods are to be added in the future, but again, the developer shouldn't rely on this structure anyway.
The default behaviour can be altered using pragmas:
(struct) makes it act purely as a packed memory structure, with no strings attached. Access is generally slower due to misalignment, but both order and packing are exactly as the user desires; this is very useful for low level programming. Methods can still be used, although virtual functions are disabled due to the absence of a virtual dispatch table.
(union) is similar to (struct), except it can only hold one value at a time. The width is that of the largest attribute.
(abstract) makes it so that objects of the custom data type cannot be instantiated, and the type can only be used as parent type. All methods defined are inherently virtual. In abstract custom data types, methods can also be pure abstract methods: as objects cannot be instantiated, the language does not require an implementation of the function; as such methods defined in abstract custom data types may be only signatures and have no body (beware, not an empty body) so that children data types throw an error if the method isn't implemented.
Custom data type variables are inherently pointers of said variable. Though, note that the * operator can NOT be used on it. The reason for this is that i don't see any reason why one would want it. So:
var Vector2 vect; // actually a pointer to a Vector2 in memory
As mentioned above, objects aren't intialized automatically. To initialize an object, there are two common ways. The first requires a standard library or a user implementation of new:
vect = new Vector2(); // fill according to constructors
It is worth noting that an empty constructor isn't automatically created for a data type. The compiler will throw an error in that case. The standard library implementation of new allocates area in the heap, then calls the constructor. The second way doesn't allocate anything, and it just calls the constructor:
vect = (Vector2)whatever; // casting to pointer requires unsafe flag
vect.Vector2();
It is worth noting that the behaviour of = for objects is to copy the pointer.
vectA = vectB; // vectA is now an alias for vectB, in the same area of memory
vectA.Vector2(vectB); // vectA is now a copy of vectB, in its own area of memory
Be aware while working with raw pointers like this, as there is no garbage collection going on. As such, this requires the unsfae flag.
Obviously, when creating an object of a type that requires generics, telling the compiler the type is mandatory, both in the constructor and in the definition.
var LinkedList<i64> myIntList = new LinkedList<i64>();
Methods are mostly up to the user, but there are two method definitions which have a specific syntax which must be followed:
fun pragmasopt dataTypeName(attributes) {}
Allowed pragmas for constructors are: (volatile), (inline), (deprecated)
fun pragmasopt ~ dataTypeName() {}
Allowed pragmas for the destructors are: (volatile), (inline)
Every custom data type also comes with default overrideable virtual methods:
Returns an object's unique hash. Should avoid collision. Used in match statements, hashmaps and == by default. Default definition is as such:
fun u64 Hash () => 0;