D supports user-defined attributes (sometimes called annotations), which are a way to add custom compile-time information to declarations that can be retrieved later by reflection. Here, we'll look at their capabilities and their limitations.
Let's execute the following steps to use user-defined attributes:
struct
or enum
to use as the attribute. A struct
attribute should have data members, as shown in the following code. An enum
attribute is best used for a simple flag:struct MyNote { string note; }
@
sigil, as shown in the following code:@MyNote("this is my note on foo") void foo() {}
__traits(getAttributes, symbol)
function. To pass the symbol to a function or template, use a compile-time parameter with the alias
keyword.is
expression. For flags, check for the validity of the type and return true
if it is present. For data annotations, such as MyNote
, check the typeof
parameter and then return the actual annotation so the data can be checked.The code is as follows:
MyNote getMyNoteAnnotation(alias F)() { foreach(attr; __traits(getAttributes, F)) { static if(is(typeof(attr) == MyNote)) return attr; } assert(0); // the annotation wasn't there } // get the note and print out the value // (the member note is of the MyNote struct) pragma(msg, getMyNoteAnnotation!(foo).note);
D's user-defined attributes never modify a type or generate new code, but do allow you to attach data to declaration that you can use later through the compile-time reflection for code generation if you wish.
The attributes are regular D types, holding regular D data. Once you retrieve them, you use the value like any other variable. To attach them, you can write @some_expression
, where some_expression
can be almost anything, as long as it gives a result at compile time. Typically, the expression is a struct
constructor @MyNote("string")
, which attaches a value to the declaration similar to the auto a = MyNote("string")
statement that assigns a value to a variable.
It is also legal to attach plain data to a declaration, such as @1 int foo;
will have an int
annotation with the value of 1
. Since int
is not a unique type and attributes are retrieved by type comparison, using naked attributes like this will lead to conflicts where one module interprets int
in one way and another sees it as something entirely different. To avoid this situation, always declare a new type for your attributes.
The __traits(getAttributes)
function returns a list of all the attributes attached to a symbol. Since the attributes may include data from other libraries about which you know nothing, it is best to loop through them and find only the ones that match your type. Once you have your data, you may do anything with it; but remember, it is just data. We'll look at a concrete example of a user-defined attribute flag in the next recipe.
A common question is how to modify a type with an attribute. This is not possible in D. You may loop over a module, looking for types with an annotation, and then declare a new one with the transformation. However, there's already a tool in the language to modify types: templates and compile-time parameters! So, instead of trying to write @NotNull Object
, use NotNull!Object
and you'll find easier success.
3.138.172.130