4

D has a fantastic module system which reduces compilation times dramatically compared to C++. According to the documentation D still provides opaque structs and unions in order to enable the pimpl idiom. My question is: How can I declare a nested struct (or union) in one module and define it in another one? What is the syntax for that?

In C++ the header would look like this

struct S { 
    ... 
    struct Impl; 
    Impl * p; 
};

and the implementation file (cpp-file) would use some interesting-looking ::-syntax like this:

#include "header.h"
struct S::Impl { 
    ... 
};

How do I implement the same in D?

Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120

3 Answers3

5

D (DMD, at least) uses .di files for declarations. They are somewhat equivalent to C .h files, however they are optional. The D compiler can generate .di files automatically (when the -H switch is specified), although I believe that currently all it does is strip function bodies and unittests.

Here's one way to achieve PImpl using .di files:

  • mod.di:

    struct S
    {
        struct I;
        I* pi;
    }
    
  • mod.d:

    struct S
    {
        struct I
        {
            int v;
        }
    
        I* pi;
    }
    

Note that it is currently your responsibility to make sure that the fields in S are the same in both the .d and .di file - if they differ, the compiled modules will have differing knowledge of how the fields are laid out, which can lead to memory corruption. The current compiler implementations do not verify if definitions match in .d and .di files.

Vladimir Panteleev
  • 24,651
  • 6
  • 70
  • 114
  • it would be the linker's responsibility to verify the memory layout assumptions because that is the only thing that knows all of them – ratchet freak Dec 09 '13 at 12:36
  • I don't think that's right. Data structures are not stored to object files. The linker does not know about types. – Vladimir Panteleev Dec 09 '13 at 12:55
  • but the compiler doesn't (need to) know about both the `.d` and `.di` file – ratchet freak Dec 09 '13 at 12:55
  • 1
    The compiler could verify that the `.d` file corresponds to the `.di` file when the `.d` file is compiled. I don't see any reason why that would be unachievable on impractical. Currently it simply doesn't do that. – Vladimir Panteleev Dec 09 '13 at 13:04
1

My question is: How can I declare a nested struct (or union) in one module and define it in another one?

To get it straight - it is intentionally impossible in D by design. It is a direct consequence of having a reliable module system - every symbol declaration is implicitly qualified by a module name it is declared inside. And for variety of reasons you can't hijack a symbol into another modules "namespace".

That said, it is not necessary to do it in same module to use pimpl approach. You can refer to CyberShadow answer for more details.

Mihails Strasuns
  • 3,783
  • 1
  • 18
  • 21
1

Another approach is based on the D's system of class's hierarchy:

all objects inherits explicitly or implicitly the Object.

So the idea is to implement OuterClass with pimpl, generate corresponding di-file, manually remove all definitions of OuterClassPrivate from di-file and change declaration of the pimpl-member.

For example:

first version of the shared library

module pimpl.mylib;

class PimplTest
{
    this()
    {
        mImpl = new PimplTestPrivate();
    }

    ~this()
    {
    }

    string sayWhat(string what)
    {
        return mImpl.ku ~ " " ~ what;
    }

    private class PimplTestPrivate
    {
        string ku = "Ku!!1";
    }

    private PimplTestPrivate mImpl;
}

test application:

module main;

import std.stdio;
import pimpl.mylib;

void main()
{
    PimplTest t = new PimplTest();
    writeln(t.sayWhat("?"));
}

Shared mylib may be built the following way (under Linux):

$ dmd -H -c mylib.d -fPIC
$ dmd -ofmylib.so mylib.o -shared -defaultlib=libphobos2.so -L-rpath=/path/to/where/shared/phobos/library/is

Then edit genereated di-file:

// D import file generated from 'mylib.d'
module pimpl.mylib;
class PimplTest
{
    this();
    ~this();
    string sayWhat(string what);

    // NOTE this
    private Object mImpl;
}

Compile the test itsel

$ dmd -c main.d /path/to/first/version/of/mylib.di
$ ln -s /path/to/first/version/of/mylib.so .
$ dmd main.o -L-l:mylib.so -defaultlib=libphobos2.so -L-rpath=/path/to/where/shared/phobos/library/is:.
$ ./main
Say: ?

Then we change mylib:

module pimpl.mylib;

import std.conv;

class PimplTest
{
    this()
    {
        mImpl = new PimplTestPrivate();
    }

    ~this()
    {
    }

    string sayWhat(string what)
    {
        return mImpl.getMessage1(mImpl.getValue(), what);
    }

    private class PimplTestPrivate
    {
        int getValue()
        {
            return 42;
        }

        string ku = "Ku!!1";

        string getMessage1(int x, string y)
        {
            return "x = " ~ to!(string)(x) ~ ", " ~ y;
        }

        double pi = 22.0/7.0;
    }

    private PimplTestPrivate mImpl;
}

Compile it and replace binary shared object (so file) of the first version of mylib with just built one. Running test application must not crash but the output will be different.

gshep
  • 628
  • 6
  • 14