Mixins are for mixing in generated code into the source code. The mixed-in code may be generated as a template instance or a string.
Mixins用于将生成的代码混合到源代码中。混合代码可以作为模板实例或字符串生成。
Code can be inserted into the program as a string import as well.
代码也可以作为字符串导入插入到程序中。
80.1 Template mixins
We have seen in the Templates and More Templates chapters that templates define code as a pattern, for the compiler to generate actual instances from that pattern. Templates can generate functions, structs, unions, classes, interfaces, and any other legal D code.
模板将代码定义为模式,以便编译器从该模式生成实际的实例。模板可以生成函数、结构、联合、类、接口和任何其他合法的D代码。
Template mixins insert instantiations of templates into the code by the mixin keyword:
mixin a_template!(template_parameters)
As we will see in the example below, the mixin keyword is used in the definitions of template mixins as well.
The instantiation of the template for the specific set of template parameters is inserted into the source code right where the mixin keyword appears.
特定模板参数集的模板实例化被插入到源代码中
mixin关键字出现的地方。
For example, let's have a template that defines both an array of edges and a pair of functions that operate on those edges:
mixin template EdgeArrayFeature(T, size_t count) {
T[count] edges;
void setEdge(size_t index, T edge) {
edges[index] = edge;
}
void printEdges() {
writeln("The edges:");
foreach (i, edge; edges) {
writef("%s:%s ", i, edge);
}
writeln();
}
}
That template leaves the type and number of array elements flexible. The instantiation of that template for int and 2 would be mixed in by the following syntax:
mixin EdgeArrayFeature!(int, 2);
For example, the mixin above can insert the two-element int array and the two functions that are generated by the template right inside a struct definition:
struct Line {
mixin EdgeArrayFeature!(int, 2);
}
As a result, Line ends up defining a member array and two member functions:
import std.stdio;
void main() {
auto line = Line();
line.setEdge(0, 100);
line.setEdge(1, 200);
line.printEdges();
}
The output:
The edges:
0:100 1:200
Another instantiation of the same template can be used e.g. inside a function:
struct Point {
int x;
int y;
}
void main() {
mixin EdgeArrayFeature!(Point, 5);
setEdge(3, Point(3, 3));
printEdges();
}
That mixin inserts an array and two local functions inside main(). The output:
The edges:
0:Point(0, 0) 1:Point(0, 0) 2:Point(0, 0) 3:Point(3, 3) 4:Point(0, 0)
Template mixins must use local imports
Mixing in template instantiations as is can cause problems about the modules that the template itself is making use of: Those modules may not be available at the mixin site.
Let's consider the following module named a. Naturally, it would have to import the std.string module that it is making use of:
module a;
import std.string; // ← wrong place
mixin template A(T) {
string a() {
T[] array;
// ...
return format("%(%s, %)", array);
}
}
However, if std.string is not imported at the actual mixin site, then the compiler would not be able to find the definition of format() at that point. Let's consider the following program that imports a and tries to mix in A!int from that module:
import a;
void main() {
mixin A!int; // ← compilation ERROR
}
Error: undefined identifier format
Error: mixin deneme.main.A!int error instantiating
For that reason, the modules that template mixins use must be imported in local scopes:
模板mixin使用的模块必须在局部作用域中导入。
module a;
mixin template A(T) {
string a() {
import std.string; // ← right place
T[] array;
// ...
return format("%(%s, %)", array);
}
}
As long as it is inside the template definition, the import directive above can be outside of the a() function as well.
Identifying the type that is mixing in
Sometimes a mixin may need to identify the actual type that is mixing it in. That information is available through this template parameters as we have seen in the More Templates chapter:
有时,mixin可能需要识别混合它的实际类型。这些信息可以通过模板参数获得。
mixin template MyMixin(T) {
void foo(this MixingType)() {
import std.stdio;
writefln("The actual type that is mixing in: %s",
MixingType.stringof);
}
}
struct MyStruct {
mixin MyMixin!(int);
}
void main() {
auto a = MyStruct();
a.foo();
}
The output of the program shows that the actual type is available inside the template as MyStruct:
The actual type that is mixing in: MyStruct
80.2 String mixins
Another powerful feature of D is being able to insert code as string as long as that string is known at compile time. The syntax of string mixins requires the use of parentheses:
D的另一个强大特性是,只要在编译时知道字符串,就可以插入代码。
mixin (compile_time_generated_string)
For example, the hello world program can be written with a mixin as well:
import std.stdio;
void main() {
mixin (`writeln("Hello, World!");`);
}
The string gets inserted as code and the program produces the following output:
Hello, World!
We can go further and insert all of the program as a string mixin:
mixin (
`import std.stdio; void main() { writeln("Hello, World!"); }`
);
Obviously, there is no need for mixins in these examples, as the strings could have been written as code as well.
The power of string mixins comes from the fact that the code can be generated at compile time. The following example takes advantage of CTFE to generate statements at compile time:
字符串混合的强大之处在于代码可以在编译时生成。下面的示例利用CTFE在编译时生成语句。
import std.stdio;
string printStatement(string message) {
return `writeln("` ~ message ~ `");`;
}
void main() {
mixin (printStatement("Hello, World!"));
mixin (printStatement("Hi, World!"));
}
The output:
Hello, World!
Hi, World!
Note that the "writeln" expressions are not executed inside printStatement(). Rather, printStatement() generates code that includes writeln() expressions that are executed inside main(). The generated code is the equivalent of the following:
注意,
writeln表达式不会在printStatement()中执行。相反,printStatement()生成的代码包含writeln()表达式,这些表达式在main()中执行。
import std.stdio;
void main() {
writeln("Hello, World!");
writeln("Hi, World!");
}
Multiple mixin arguments
As long as they are all known at compile time, mixin can take multiple arguments and automatically concatenates their string representations:
只要它们在编译时都是已知的,mixin就可以接受多个参数并自动连接它们的字符串表示。
mixin ("const a = ", int.sizeof, ";");
This can be more convenient compared to using e.g. a format expression:
mixin (format!"const a = %s;"(int.sizeof)); // Same as above
Debugging string mixins
Because generated code is not readily visible as a whole in source code, it can be difficult to identify causes of compilation errors with mixin expressions. To help with debugging string mixins, there is the dmd compiler switch -mixin, which writes all mixed-in code to a specified file.
为了帮助调试字符串混合,
dmd编译器有一个-mixin开关,它将所有混合代码写入指定的文件。
Let's consider the following program that has a syntax error in code that is being mixed in. It is not obvious from the compiler error that the syntax error is the missing semicolon at the end of the definition of the struct member:
string makeStruct(string name, string member) {
import std.format;
return format!"struct %s {\n int %s\n}"(name, member);
}
mixin (makeStruct("S", "m")); // ← compilation ERROR
void main() {
}
When compiled with the -mixin switch, the compilation error would point at a line inside the specified file (mixed_in_code in the example below):
$ dmd -mixin=mixed_in_code deneme.d
mixed_in_code(154): Error: semicolon expected, not }
Along with all other code that are mixed-in by the standard library, there would be the following code at the specified line inside file mixed_in_code:
[...]
// expansion at deneme.d(6)
struct S {
int m
} ← Line 154
Another option for debugging string mixins is pragma(msg), which would print the generated code during compilation. This option is less practical because it requires replacing the mixin keyword with pragma(msg) temporarily for debugging:
pragma(msg, makeStruct("S", "m"));
80.3 Mixin name spaces
It is possible to avoid and resolve name ambiguities in template mixins.
For example, there are two i variables defined inside main() in the following program: one is defined explicitly in main and the other is mixed in. When a mixed-in name is the same as a name that is in the surrounding scope, then the name that is in the surrounding scope gets used:
import std.stdio;
template Templ() {
int i;
void print() {
writeln(i); // Always the 'i' that is defined in Templ
}
}
void main() {
int i;
mixin Templ;
i = 42; // Sets the 'i' that is defined explicitly in main
writeln(i); // Prints the 'i' that is defined explicitly in main
print(); // Prints the 'i' that is mixed in
}
As implied in the comments above, template mixins define a name space for their contents and the names that appear in the template code are first looked up in that name space. We can see this in the behavior of print():
如上面的注释所示,模板mixins为其内容定义了一个名称空间,出现在模板代码中的名称首先在该名称空间中查找。
42
0 ← printed by print()
The compiler cannot resolve name conflicts if the same name is defined by more than one template mixin. Let's see this in a short program that mixes in the same template instance twice:
如果同一名称由多个模板混合定义,编译器无法解决名称冲突。
template Templ() {
int i;
}
void main() {
mixin Templ;
mixin Templ;
i = 42; // ← compilation ERROR
}
Error: deneme.main.Templ!().i at ... conflicts with
deneme.main.Templ!().i at ...
To prevent this, it is possible to assign name space identifiers for template mixins and refer to contained names by those identifiers:
为了防止这种情况,可以为模板mixin分配名称空间标识符,并引用这些标识符所包含的名称。
mixin Templ A; // Defines A.i
mixin Templ B; // Defines B.i
A.i = 42; // ← not ambiguous anymore
String mixins do not have these name space features. However, it is trivial to use a string as a template mixin simply by passing it through a simple wrapper template.
字符串混合没有这些名称空间特性。然而,通过简单的包装器模板将字符串作为模板混合使用是很简单的。
Let's first see a similar name conflict with string mixins:
void main() {
mixin ("int i;");
mixin ("int i;"); // ← compilation ERROR
i = 42;
}
Error: declaration deneme.main.i is already defined
One way of resolving this issue is to pass the string through the following trivial template that effectively converts a string mixin to a template mixin:
解决这个问题的一种方法是通过下面简单的模板传递字符串,它可以有效地将字符串mixin转换为模板mixin。
template Templatize(string str) {
mixin (str);
}
void main() {
mixin Templatize!("int i;") A; // Defines A.i
mixin Templatize!("int i;") B; // Defines B.i
A.i = 42; // ← not ambiguous anymore
}
80.4 String mixins in operator overloading
We have seen in the Operator Overloading chapter how mixin expressions helped with the definitions of some of the operators.
In fact, the reason why most operator member functions are defined as templates is to make the operators available as string values so that they can be used for code generation. We have seen examples of this both in that chapter and its exercise solutions.
80.5 Mixed in destructors
It is possible to mix in multiple destructors to a user defined type. Those destructors are called in the reverse order of the mixin statements that added them. This feature allows mixing in different resources to a type, each introducing its own cleanup code.
可以在用户定义的类型中混合使用多个析构函数。调用这些析构函数的顺序与添加它们的mixin语句的顺序相反。这个特性允许在一个类型中混合使用不同的资源,每个资源都引入自己的清理代码。
import std.stdio;
mixin template Foo() {
~this() {
writeln("Destructor mixed-in by Foo");
}
}
mixin template Bar() {
~this() {
writeln("Destructor mixed-in by Bar");
}
}
struct S {
~this() {
writeln("Actual destructor");
}
mixin Foo;
mixin Bar;
}
void main() {
auto s = S();
}
Destructor mixed-in by Bar
Destructor mixed-in by Foo
Actual destructor
Due to a bug as of this writing, the same behavior does not apply to other special functions like constructors. Additionally, a destructor mixed in by a string mixin does conflict with the existing destructor of the type.
80.6 Importing text files
It is possible to insert contents of text files into code at compile time. The contents are treated as string literals and can be used anywhere strings can be used. For example, they can be mixed in as code.
可以在编译时将文本文件的内容插入到代码中。内容被视为字符串字面量,可以在任何可以使用字符串的地方使用。例如,它们可以作为代码混合在一起。
For example, let's assume there are two text files on the file system named file_one and file_two having the following contents.
file_one:
Hello
file_two:
s ~= ", World!";
import std.stdio;
writeln(s);
The two import directives in the following program would correspond to the contents of those files converted to string literals at compile time:
void main() {
string s = import ("file_one");
mixin (import ("file_two"));
}
Text file imports (a.k.a. string imports) require the -J compiler switch which tells the compiler where to find the text files. For example, if the two files are in the current directory (specified with . in Linux environments), the program can be compiled with the following command:
文本文件导入需要
-J编译器开关,它告诉编译器在哪里找到文本文件。
$ dmd -J. deneme.d
The output:
Hello, World!
Considering the file contents as string literals, the program is the equivalent of the following one:
void main() {
string s = `Hello`; // ← Content of file_one as string
mixin (`s ~= ", World!";
import std.stdio;
writeln(s);`); // ← Content of file_two as string
}
Further, considering the mixed-in string as well, the program is the equivalent of the following one:
void main() {
string s = `Hello`;
s ~= ", World!";
import std.stdio;
writeln(s);
}
80.7 Example
(Note: Specifying predicates as strings was used more commonly before the lambda syntax was added to D. Although string predicates as in this example are still used in Phobos, the => lambda syntax may be more suitable in most cases.)
Let's consider the following function template that takes an array of numbers and returns another array that consists of the elements that satisfy a specific condition:
int[] filter(string predicate)(int[] numbers) {
int[] result;
foreach (number; numbers) {
if (mixin (predicate)) {
result ~= number;
}
}
return result;
}
That function template takes the filtering condition as its template parameter and inserts that condition directly into an if statement as is.
For that condition to choose numbers that are e.g. less than 7, the if condition should look like the following code:
if (number < 7) {
The users of filter() template can provide the condition as a string:
int[] numbers = [ 1, 8, 6, -2, 10 ];
int[] chosen = filter!"number < 7"(numbers);
Importantly, the name used in the template parameter must match the name of the variable used in the implementation of filter(). So, the template must document what that name should be and the users must use that name.
Phobos uses names consisting of single letters like a, b, n, etc.