Type Namespaces
This section discusses D namespaces and namespace issues related to types. 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 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 running on the system. A single D program may
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 available to DTrace and the D compiler. To manage this
situation, the D compiler associates each type with a namespace identified by the
containing program object. Types from a particular program object can be accessed by
specifying the object name and backquote (`) scoping operator in any type name.
For example, if a kernel module named foo contains the following C type
declaration:
typedef struct bar {
int x;
} bar_t;
then the types struct bar and bar_t could be accessed from D using the
type names:
struct foo`bar foo`bar_t
The backquote 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 acquired using
the C preprocessor cpp(1) using the dtrace -C option will be processed by and
added to the C scope. As a result, you can include C
header files containing type declarations which 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 uint32_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 your D program consisting of member types
from other namespaces, the member types will be 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 backquote operator, the compiler searches the set of active
type namespaces to find a match 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 ascending order by
kernel module ID. This ordering guarantees that the binary objects that form the
core kernel are searched before any loadable kernel modules, but does not guarantee
any ordering properties among the loadable modules. You should use the scoping operator when
accessing types defined in loadable kernel modules to avoid type name conflicts with
other kernel modules.
The D compiler uses compressed ANSI-C debugging information provided with the core Solaris
kernel modules in order to automatically access the types associated with the operating
system source code without the need for accessing the corresponding C include files.
This symbolic debugging information might not be available for all kernel modules on
your system. The D compiler will report an error if you attempt to
access a type within the namespace of a module that lacks compressed C
debugging information intended for use with DTrace.