2.13.4 Type Namespaces

In traditional languages such as ANSI C, type visibility is determined by whether a type is nested inside of a function or other declaration. Types declared at the outer scope of a C program are associated with a single global namespace and are visible throughout the entire program. Types that are defined in C header files are typically included in this outer scope. Unlike these languages, D provides access to types from multiple outer scopes.

D is a language that facilitates dynamic observability across multiple layers of a software stack, including the operating system kernel, an associated set of loadable kernel modules, and user processes that are running on the system. A single D program can instantiate probes to gather data from multiple kernel modules or other software entities that are compiled into independent binary objects. Therefore, more than one data type of the same name, perhaps with different definitions, might be present in the universe of types that are available to DTrace and the D compiler. To manage this situation, the D compiler associates each type with a namespace, which is identified by the containing program object. Types from a particular program object can be accessed by specifying the object name and the back quote (`) scoping operator in any type name.

For example, for a kernel module named foo that contains the following C type declaration:

typedef struct bar {
  int x;
} bar_t;

The types struct bar and bar_t could be accessed from D using the following type names:

struct foo`bar

The back quote operator can be used in any context where a type name is appropriate, including when specifying the type for D variable declarations or cast expressions in D probe clauses.

The D compiler also provides two special, built-in type namespaces that use the names C and D, respectively. The C type namespace is initially populated with the standard ANSI C intrinsic types, such as int. In addition, type definitions that are acquired by using the C preprocessor (cpp), by running the dtrace -C command, are processed by and added to the C scope. As a result, you can include C header files containing type declarations that are already visible in another type namespace without causing a compilation error.

The D type namespace is initially populated with the D type intrinsics, such as int and string, as well as the built-in D type aliases, such as uint64_t. Any new type declarations that appear in the D program source are automatically added to the D type namespace. If you create a complex type such as a struct in a D program consisting of member types from other namespaces, the member types are copied into the D namespace by the declaration.

When the D compiler encounters a type declaration that does not specify an explicit namespace using the back quote operator, the compiler searches the set of active type namespaces to find a match by using the specified type name. The C namespace is always searched first, followed by the D namespace. If the type name is not found in either the C or D namespace, the type namespaces of the active kernel modules are searched in load address order, which does not guarantee any ordering properties among the loadable modules. To avoid type name conflicts with other kernel modules, you should use the scoping operator when accessing types that are defined in loadable kernel modules.

The D compiler uses the compressed ANSI C debugging information that is provided with the core Linux kernel modules to automatically access the types that are associated with the operating system source code, without the need to access the corresponding C include files. Note that this symbolic debugging information might not be available for all kernel modules on your system. The D compiler reports an error if you attempt to access a type within the namespace of a module that lacks the compressed C debugging information that is intended for use with DTrace.