Nick Sabalausky is a long-time D user and contributor. He is the maintainer of mysql-native and Scriptlike. In this post, he presents a way to use a specific D language feature to improve error messages involving aliased types.
In the D programming language, alias
is a common and handy feature that can be used to provide a simple name for a complex and verbose templated type.
As an example, consider the case of an algebraic type or tagged union:
// A type that can be either an int or a string Algebraic!(int, string) someVariable;
That’s a fairly simple example. Much more complicated type names are common in D. This sort of thing can be a pain to repeat everywhere it’s used, and can make code difficult to read. alias
is often used in situations like this to create a simpler shorthand name:
// A type that can be either an int or a string alias MyType = Algebraic!(int, string); // Ahh, much nicer! MyType someVariable;
There’s one problem this still doesn’t solve. Anytime a compiler error message shows a type name, it shows the full original name, not the convenient easy-to-read alias. Instead of errors saying MyType
, they’ll still say Algebraic!(int, string)
. This can be especially unfriendly if MyType
is in the public API of a library and happens to be constructed using some private, internal-only template.
That can be fixed, and error messages forced to provide the customized name, by creating MyType
as a separate type on its own, rather than an alias. But how? If this was C or C++, typedef
would do the job nicely. There is a D equivalent, std.typecons.Typedef!T
, which will create a separate type. But naming the type still involves alias
, which just leads back to the same problem.
Luckily, D has another feature which can help simulate a C-style typedef
: alias this
. Used inside a struct (or class), alias this
allows the struct to be implicitly converted to and behave just like any one of its members.
Incidentally, although alias
and alias this
are separate features of the language, they do have a shared history as their names suggest. Originally, alias
was intended to be a variation on C’s typedef
, one which would result in two names for the same type instead of two separate types. At the time, D had typedef
as well, but it was eventually dropped as a language feature in favor of a standard library solution (the aforementioned std.typecons.Typedef
template). As a variant of typedef
, alias
used the same syntax (alias TypeName AliasName;
). Later, alias
spawned the alias this
feature, which was given a similar syntax: alias memberName this
. When alias
gained its modern syntax (alias AliasName = TypeName
), a lengthy debate resulted in keeping the existing syntax for alias this
.
Here is how alias this
can be used to solve our problem:
// A type that can be either an int or a string struct MyType { private Algebraic!(int, string) _data; alias _data this; } // Ahh, much nicer! And now error messages say "MyType"! MyType someVariable;
There’s an important difference to be aware of, though. Before, when using alias
, MyType
and Algebraic!(int, string)
were considered the same type. Now, they’re not. Is that a problem? What does that imply? Mainly, it means two things:
-
- Although this doesn’t affect any actual code, it can mean the compiler generates extra, duplicate template instantiations. If
MyType
is passed to one template, and somewhere elseAlgebraic!(int, string)
is passed to the same template, the compiler will now generate two separate template instantiations instead of just one.
In practice though, this shouldn’t be a problem unless you’re already in a genuine template-bloat situation and are trying to reduce template instantiations. Usually, this won’t be an issue. - Although the
alias this
meansMyType
can still be implicitly converted toAlgebraic!(int, string)
, the other way around no longer works. AnAlgebraic!(int, string)
can no longer be implicitly converted to aMyType
.
Arguably, this can be considered a good thing if you believe, as I do, in using domain-specific types. But in any case, you can still manually convert the original type to yourMyType
with the basic built-in struct constructor:Algebraic!(int, string) algebVar; auto myVar = MyType(algebVar);
So when you’re aliasing a large, complicated type name to a simpler name, consider using a struct and
alias this
instead, especially if it’s a type on offer in a library. There’s little downside, and it will greatly improve the readability of error messages for both yourself and your library’s users. - Although this doesn’t affect any actual code, it can mean the compiler generates extra, duplicate template instantiations. If