Type Declarations
Many programming solutions find it very useful to declare new types customized to handle specific data. This allows the system to generate efficient code. It can also make code easier to read and use. The techniques that the Wolfram Compiler provides for new types are explored in this section.
Product
A product type is a compound type that contains other types. Product types are also known as structs or records and are a key element of programming.
Product types in the Wolfram Compiler are flexible, supporting value and reference semantics as well as automatic memory management. A key aspect is the ability to define polymorphic products, i.e. types that are parameterized in their contents.
An initial example shows a declaration for "DemoProduct", a product that contains two fields. Note the use of the labels, which are useful for access:
This function creates an instance of "DemoProduct", initializing both of the elements. These are then extracted and added together:
Objects of this type are created with the default settings of declarations for products; this is for reference objects with automatic memory management. An object that is a reference is one that is held in memory allocated in virtual memory on the system. These can be shared in different locations. It also means that it is necessary to be freed when no longer used. This is done automatically by the automatic memory management system.
This adds a "Creator" setting to the type declaration. It names a function that will be called with CreateTypeInstance. An implementation for the creator has to be given as well:
Now calls to CreateTypeInstance use the new function:
This adds an operation to the type declaration. Operations are functions for a product that are called with a convenient syntax. An implementation for any operation has to be given:
Now the type product is created and the operation is called:
By default, a product is a reference object; it is held as a memory address that is allocated when the product is created. By default, this is memory managed, so no special work is required to free the object. On the other hand, a value product does not allocate memory. Issues to do with memory management are covered in a different section.
Returning to Evaluator
If you want to work with a product type, you may well want to return it to the Wolfram evaluator and also to write other compiled functions that accept it as input. This can be done very easily for a reference product by giving it the "DefaultSerializable" type class. This means that the object can be embedded into a Wolfram expression that wraps the object and allows it be passed to and from the rest of the Wolfram Language.
Declare a type that has the default serializable abstract type:
Compile a function that returns an instance of the product and other functions that use it:
Create an instance of the object. It becomes a data structure expression:
You can call different functions passing in the object:
Compile a function that sets one of the fields of the product:
Set the f1 field to a different value:
The updated value can be seen:
The topic of converting objects into expressions and back is an important and deep one. It is covered in more detail in the section on Expressions.
Reference and Value Semantics
Instances of a product type are, by default, reference objects, i.e. they are held as a reference to a block of memory. This has to be allocated and freed. By default, reference product types have automatic memory management.
Objects held as references are convenient, since they can be shared in more than one place and fit with other features of the Wolfram Language such as expressions and packed arrays. They do have some overhead because they need memory reads and writes. If they are memory managed, this also has some overhead.
It is also possible to have product types that are values. An object created as a value is not a reference to a block of memory. A value object has the advantage that it can be held in registers and does not need any memory management. It has the disadvantage that if it is large, then passing it into and out of functions can be time consuming; also, it cannot refer back to itself. Another advantage is that the low-level compiler functions can often generate more highly optimized code for value type products. This means they can be useful for very small, short-lived objects in performant code.
Whether a product type has reference or value semantics is a property of the product and set when the product is declared using the "ReferenceSemantics" option.
This declares a product that is not a reference:
Compile a function that uses the value type product:
Memory Management
Product types are, by default, given automatic memory management. This means that when they are no longer used, the memory they consume is freed. This can be disabled with the "MemoryManaged" option.
CreateTypeInstance reference types created by CreateTypeInstance allocate memory. By default, the memory is managed automatically. This is controlled by the "MemoryManaged" option.
The following type does not manage its memory automatically:
This might be done to make a structure layout that can be passed into another language.
More information on memory management is found in another section.
Initialization
When instances of a type product are created, values for the different fields are given, tagged by the field name. If any fields are omitted, they are given a default zero value.
This declares that "DemoType" is a type product that contains two elements. Note the use of the labels, which are useful for access:
This function creates an instance of "DemoType", but initializes only one of the fields. The other field is extracted and returned:
This returns the uninitialized value for the field:
A better solution is to add a "Creator" setting to the declaration. An alternative is to make a custom declaration of CreateTypeInstance for a product that initializes things explicitly in the way that is desired. This uses "TypeSpecifier" in the type:
Now the field is initialized explicitly:
Polymorphic Products
Polymorphic product types are types where some of the fields have types specified by polymorphic parameters. This leads to very flexible code with a high degree of abstraction.
This declares "DemoType" as a product that contains two elements; one of them is given by a type parameter, "elem":
This declares a creation function:
This function creates an instance of "DemoType" initialized with the two arguments. These are then extracted and added together. In this case, the type is parametrized by "Real64":
In this example, the type is parametrized by "Integer128":
This example returns the type of the object created. It is parametrized by "Real64":
Here the type is parametrized by "Integer128":
Macro
A type macro is a simple way to extend the type system, for example, making shorter versions of types.
This declares that "I64" is a macro for "Integer64":
This declaration is just a representation of a type; it does not do anything until used in a compilation.
When a macro is used, it behaves just like "Integer64":
An important aspect of type macros is that they are converted into their target type. This means that any functions defined for the target also work for the macro. Here, multiplication works:
Also, it is allowed to combine a macro and the target:
On compilation, the macros are converted into their targets, as seen here with TypeOf:
Macros can have targets that are compound:
The macro is used; it behaves just like a "RawPointer" of "Integer64":
Alias
A type alias is another way to extend the type system. It is different from a macro in that resolution into the target is only done at the very end of code generation.
A common usage of an alias is to give a type name to raw memory that comes from an external library. The following would be an example:
This is discussed in more detail in the documentation for calling external libraries.
Abstract
Abstract types serve two primary purposes: organizing related types and defining shared implementations. As demonstrated in polymorphic function declarations, they establish type relationships while remaining non-instantiable - only concrete types (like product types) can create objects. However, the type of an object can belong to an abstract type. Abstract types additionally form hierarchical relationships through inheritance.
The following shows information on the "Integers" abstract types:
The following shows the types that are instances of "Integers":
When a product type is declared, this can also include abstract types that it implements. This is a very convenient way to add behavior to a type. An example of this was shown early in this section, where the "DefaultSerializable" type was used to allow a product to interact with expressions in a very convenient way.
In addition to the built-in abstract types, you can declare your own. You can also add "Operations" for the abstract type, which is a powerful way to specify the behavior of any type that implements it.
Extending Behavior
This declares an abstract type and a function that takes an instance of the type, prints it out and returns it:
It is not legal to call the function on something that is not an instance of the abstract type:

Declare a type that implements the "Simple" abstract type and also the "DefaultSerializable" type class:
Compile a function that creates an instance of the abstract type:
Now it is possible to compile a function that calls the demo function on an instance of the type:
Create an instance of the type:
The test function works for the type:
This is a convenient way to add functionality: any type that implements an abstract type can work for polymorphic functions that are written in terms of that abstract type.
Adding Operations
Abstract types can also declare operations. This has a number of advantages. The operations can be useful to demonstrate the intent of the type class. For example, an iterator type class can have operations that define what functionality types that implement the type class need to provide.
In the Wolfram Compiler, operations on type classes can give default implementations; they can also specify required operations.
Required Operations
The following creates an abstract type with an operation and a product type that implements the abstract type:
When these declarations are used, there is an error; the operation declared on the abstract type has not been found for the product (this example creates a compiler environment for demonstration purposes):

This is an implementation of the operation:
Now a compilation that uses the function works:
To test this, a function that creates an instance is created:
Calling the operation on the instance gives the desired behavior:
It is very useful to use an abstract type to denote functionality that must be provided by types that implement the abstract type.
Default Implementations of Operations
Operations on abstract types can also have default implementations. Any implementation like this must necessarily be written polymorphically.
The following creates an abstract type with an operation; it gives a function for the operation and an implementation of the operation. This is a default implementation for the "Open" operation:
A type that implements the abstract type. Since there is a default implementation of the operation, it does not need to provide one:
Compile a function that creates an instance:
Compile a function that calls the operation; the implementation was defined with the abstract type:
Calling the code shows that the implementation has come from the abstract type:
It is also possible to override the default implementation:
Compile a call that will use the override:
Calling the code shows that the implementation has been overridden:
It is also possible to have chains of abstract types and to override operations as you pass along the chain.
It is very useful to use an abstract type to provide default implementations of code. It means that any type that implements the abstract type will be able to use all the code. It is also useful to be able to override the default implementations if this is required.