I've gone back to the drawing board on sumtypes, and I had some ideas yesturday based upon feedback from the last couple of years.
Unlike the other designs that have been proposed, this one is an inline to the type definition instead of having a declaration. It gives enum declarations without a value a type (error currently), that is non-unique to the declaration.
Matching is not added here, but my previous DIP for them could be made to work for it.
Elements
A sumtype contains zero or more elements.
Each element may have a name.
An element type + name pair must be unqiue in the element list, and a name may only appear once.
Valid: alias S = sumtype (int, int i);
Invalid: alias S = sumtype(int i, float i);
Any element whose type is an enum type, will also have the element's name set to that of its identifier.
Set ops
Two sumtypes may be merged together using the +
operator.
alias A = sumtype(int);
alias B = sumtype(float);
alias C = A + B;
And subtracted from with -
:
alias C = sumtype(int, float);
alias B = sumtype(float);
alias A = C - B;
The normal restrictions within a sumtype elements apply before and after a setop has occured.
Combine with alias assignment for fine grained control:
alias Result = sumtype();
static foreach(Type; Input) {
Result += sumtype(Type);
}
Duplicates are ignored during merging.
Construction
A sumtype maybe constructed in one of three situations:
- Sumtype initialization syntax:
Type(Expression)
orType(name: Expression)
. - Variable declaration:
Type var = Expression;
- Return:
return Expression;
- Function call:
func(1);
wherevoid func(sumtype(int) param)
For function calls, the argument to parameter matching will use conversion, and will be considered less of a match than a exact one.
Assignment
A sumtype may be assigned to another, that has an comparable element list.
alias A = sumtype(int);
alias B = sumtype(int | string);
B b = A(5);
Assigning a sumtype to another will have preference over initialization.
alias A = sumtype(int);
alias B = sumtype(int, A);
B initialization = 8;
B assignment = A(9);
Enum Type
An enum without a value, is given a non-unique type based upon its identifier.
enum None;
pragma(msg, typeof(None)); // __enumtype("None")
static assert(is(typeof(None) == __enumtype));
An enum type may be used as its type, when the grammar requires a type:
None none = None;
As its size is zero, any variables that are of these types are dummy and can replace the existing practice of void[0] storage;
. They do not contribute to field layouts.
An assignment of true
will succeed, although will be no-op.
None none = true;
The enum type __enumtype("None")
will have an instance in object.d.
The mangling of an enum type does not include the module it is in.
Comparison
To check the type of a sumtype against a known type, use an is expression.
assert(sumtype(int).init is int);
Other comparisons i.e. ==
are done by compiler hook by matching and comparing the values if the tags match.
Casting
Casting a sumtype results in a read barrier to check the tag matches the requested type on read. There is no read barrier on write.
The result of a cast cannot be passed around by-ref or taken a pointer to.
Properties
A sumtype holds the properties:
tag
that of the tagged unionstorage
the block of storage (@system
), typed asvoid[X]
types
holds a sequence of types, which are the types for the elements.names
holds a sequence of strings, which are the names for the elements.copyconstructor
, the function pointer for the copy constructor@system
.destructor
, the function pointer for the destructor@system
.
All properties are assignable in non-@safe
code except names
and types
.
Layout
The layout of a sumetype is variable length it is as follows:
size_t
tagvoid function(ref new_, ref old_)
Copy constructorvoid function(ref old_)
Destructorvoid[X]
Storage
The tag is a hash of the fully qualified name of the type + name.
The copy constructor and destructor will work, as long as their arguments are pointing at storage. In practice the compiler will need to inject a null check before calling. The calling convention of the functions matches that of methods.
Attributes used on the copy constructor and destructor will be the common denominator between all the elements who have copy constructors and destructor respectively.
These are optional if none of the element types use a copy constructor or destructor.
Grammar
BasicType:
+ SumType
TypeSpecialization:
+ sumtype
+ __enumtype
+ SumType:
+ sumtype ( SumTypeElements|opt )
+ SumTypeElements:
+ SumTypeElements '|' SumTypeElement
+ SumTypeElement
+ SumTypeElement
+ Type Identifier
+ Type
Example
enum None;
alias Animal = sumtype(None | Cat | Dog dog);
struct Cat {
}
struct Dog {
}
void main() {
Animal animal = Animal(Cat());
animal.dog = Dog();
cast(Cat)animal = Cat();
writeln(cast(Cat)animal);
animal.None = true;
assert(animal is None);
}
void someFunc(Animal animal) {
import std.stdio;
writeln(animal.tag); // some hash number
}