The C preprocessor makes it possible to conditionally compile certain blocks of code using #define
and related directives. Once again, D achieves similar results using built-in compile-time statements, such as version
, debug
, and static if
.
A version
condition is used to instruct the compiler to generate code for anything in the version
block only if the specific condition is defined. Here's an example:
version(Windows) pragma(msg, "We are compiling on Windows."); else version(OSX) pragma(msg, "We are compiling on a Mac OS X system."); else version(Posix) pragma(msg, "We are compiling on a Posix system.");
This example uses the predefined versions Windows
, OSX
, and Posix
. Swap the order of the Posix
and OSX
versions and the Posix
block, not the OSX
block, will run on Mac OS X. Remove the else
statements and then both the Posix
and OSX
blocks will compile on Mac. Posix
is defined on all POSIX systems, which includes Linux, Mac OS X, and the various BSDs. In addition to Windows and OSX, other system-specific versions include linux
, FreeBSD
, OpenBSD
, NetBSD
, DragonFlyBSD
, BSD
(for other flavors of BSD), Solaris
, and more. You can find a list of predefined versions at http://dlang.org/version.html#predefined-versions.
The fact that the linux
version isn't capitalized is sort of an accident of history. linux
is a predefined preprocessor symbol with the GCC compiler. Back in the D1 days, the same symbol was included in D under the assumption that Linux programmers would find it familiar. Over the years, some users have asked that it be deprecated and replaced with the capitalized form, but that hasn't happened as a great deal of D code already uses it. As such, it remains an anomaly among the predefined versions. It's also an occasional source of bugs. One such bug actually made it into Phobos, where some Linux-specific networking code was versioned with Linux
instead of linux
!
version
does not create a new scope; anything declared inside a version
block belongs to the enclosing scope. For example:
module timestuff; // These imports are at global scope. version(Windows) import core.sys.windows.windows; else import core.sys.posix.time; void doSomeTimeStuff() { // These variables are in function scope version(Windows) { SYSTEMTIME sysTime; // Do something with sysTime } else { timeval tv; // Do something with tv } int hour = sysTime.wHour; // Compiles only on Windows! }
New D programmers are often surprised that they are unable to use version
with Boolean expressions, for example version(Windows || linux)
. Such a feature has been requested, but was rejected on the grounds that it leads to error-prone code. One way to handle this is to use a version specification. These can be declared in module scope, never in a local scope, and allow you to specify new versions in code.
version(Windows) version = WindowsOrLinux; else version(linux) version = WindowsOrLinux;
Now version(WindowsOrLinux)
can be used with one major caveat: version specifications only exist within the module in which they are declared. To use WindowsOrLinux
in multiple modules, the preceding code must be included at the top of every module that needs it; it can't be implemented once and imported everywhere. Note that using a version before it is set is an error.
version(DoIt) pragma(msg, "DoIt!"); version = DoIt; // Error
In addition to predefined operating system versions, there are versions for the currently recognized D compilers, CPU architectures, endianness, feature availability, and more. Additionally, version(unittest)
is enabled only when -unittest
has been passed to the compiler, version(assert)
is satisfied only when asserts are enabled, and version(none)
can be used to disable a block of code.
D allows custom versions to be specified on the command line with the –version
compiler switch. For example:
dmd -version=SayHello foo.d
With this command line, the following snippet will print Hello, World!
.
version(SayHello) writeln("Hello, World!"); else writeln("I have nothing to say.");
It's also possible to specify an integer version
, which the compiler interprets as a version level. Any code in such a block will only be compiled when the number is greater than or equal to the number specified.
version(10) pragma(msg, "Ten is enabled!");
This form must use integer literals and can be specified either on the command line or with a version specification in code. To see the preceding message:
dmd -version=10 foo.d
Anything in a
version
block must be syntactically valid D. This can have unexpected consequences. It's possible when using different compilers, or even different versions of the same compiler, that code in a version block will not be syntactically valid, causing a compiler error. For this reason, some prefer to use version(none)
to disable unused code rather than commenting it out; it helps ensure the code will not go stale.
Where version
is intended to be used to facilitate porting across different platforms and configuring different program features, debug
is intended to enable the inclusion of code used for debugging. It's only available when -debug
is passed on the command line. Here's a simple example. Note that it uses no identifiers and no numeric debug level.
debug writeln("Debugging enabled."); else writeln("Debugging disabled.");
Compile this with -debug
and the first line will print; compile without that flag and the second line will print. If you need them, you can also use identifiers and numeric levels.
debug(Graphics) writeln("Graphics debugging enabled."); debug(10) writeln("Debug level 10 enabled.");
Identifiers and levels can be specified in code using a debug specification, for example debug=10
, where they do not create a new scope, or on the command line, for example -debug=Graphics
. A debug specification is only valid for the module in which it is declared, but using the command-line flag enables it for the entire program. The meaning of a debug identifier or level is entirely up to you; that is, specifying –debug=Graphics
does not enable any automatic debugging for a graphics
package or module; any code you'd like enabled only in that case must be wrapped in debug(Graphics)
blocks. -debug
is shorthand for -debug=1
and, in source code, debug
means debug(1)
.
The static if
condition is a compile time version of the if
statement. It can be used in any scope, including module scope, and can contain multiple else static if
branches and a single, optional else
at the end. When the condition of any branch is met, any code inside its block will be included in the final binary. No branch in a static if
chain creates a new scope, so any variables declared inside will belong to the enclosing scope. As with other statements, if the block contains a single expression or statement, the braces can be omitted.
There are many uses for static if
, but one that I've found particularly helpful is to create Boolean conditions for version combinations. Let's redo the version(WindowsOrLinux)
example from earlier. Since conditions in a static if
are evaluated at compile time, they need to be compile-time expressions. For that, we'll enlist the help of a couple of manifest constants.
version(Windows) { enum sysWindows = true; enum sysLinux = false; } else version(linux) { enum sysWindows = false; enum sysLinux = true; } else { enum sysWindows = false; enum sysLinux = false; }
The preceding snippet is implemented once at module scope and that module is then imported anywhere these constants are needed. A static if
statement with a logical OR condition is used to test if the platform is Windows or Linux.
void main() { static if(sysWindows || sysLinux) writeln("Windows or Linux!"); else writeln("Neither Windows nor Linux!"); }
Any compile-time expression can be used as a static if
condition. Don't forget that most of the built-in type properties are compile-time values. For example, the following configures a
struct
based on the host platform's architecture.
struct SomethingSilly { static if(size_t.sizeof == 8) // 64-bit double value; else static if(size_t.sizeof == 4) // 32-bit float value; else // Future proof pragma(msg, "Unsupported architecture."); }
3.16.212.217