Working on a large(r) project in D, I’ve
started to notice some.. irritation and clumsiness with modules. The spec for modules is not very
extensive, either. So for the past
day or so, I’ve been working on figuring out just how the modules work (or
don’t). Here are the problems I’ve
come across.
Naming
The name deduction process for modules seems to go like this, as defined in the spec:
if file has a “module” declaration
{
use “module” declaration name
}
else
{
ambiguous? spec says:
“the module name is taken to be the same name (stripped of path and extension)”
and at the same time:
“The packages correspond to directory names in the source file path.”
}
This can be confusing. I could have a module with a filename of /modules/math/vector.d, but have a module declaration in the file that calls it “3dmath.vectors” or something. I understand why this is necessary – some file systems are case-sensitive, and it’d be a pain to have different cases for the directory name and the “module” name (and in fact, there may be computers which don’t have the concept of directories, making the directory system worthless there).
Perhaps the directory system should be removed, if for nothing else but consistency. That is, all modules must have a module declaration for them to be importable. This would still allow modules to be named differently from their directories, but it’d be consistent – you’d have to look in a module to see what it’s called. No more guessing whether to call it by its directory name or its “module” name.
Lastly, the directory naming ambiguity is described in the next section..
Importing
I’ve come across two rather confusing errors with this naming stuff as well as some other.. issues. My directory tree looks like this:
/dtest
+---dtest.d
+---/mod
+---+---mod1.d
The contents of the files:
dtest.d
import
std.stdio;
import
mod1;
void
main()
{
writefln(x);
}
mod1.d
int
x=5;
This compiles and runs. I’m kind of confused why it doesn’t require me to write import mod.mod1;, as it should be using the directory naming system (and no, /dtest/mod is not in my include path). In fact, I can nest the mod1.d in as many layers of directories as I want and I’ll still be able to import it as just mod1.
In fact, if I try to use import mod.mod1, I get the following, confusing error:
dtest.d(2): module mod1 is in
multiply defined
What on earth is that supposed to mean? Is the “in” just a typo? If so, where is the module multiply defined? Why can’t I import the module like this, if the directory structure is like so?
If, however, I remove mod1.d from the compiler command line, I must use import mod.mod1. Of course, the linker then complains about x being an unresolved reference. Why are there two systems of import naming depending on whether or not I’m compiling the module? Yet another reason to question the directory naming system.
Even stranger: if I change my import to import mod.mod1, and add a module declaration to the module called module mod1, I get yet another incomprehensible error:
dtest.d(2): module mod1 is in
multiple packages mod1
If I use import mod1, on the other hand, it works fine.
And yet again, if the module is not being compiled, I must explicitly qualify the module name as mod.mod1.
Oh, and this one takes the cake:
dtest.d
import
std.stdio;
// notice what I call it
here!
import
mod.mod1;
void
main()
{
writefln(x);
}
mod1.d
// notice the name is now
mod2!
module
mod2;
int
x=5;
This compiles and runs when mod1 is not being compiled. The linker whines of course. Apparently, the “module” declaration is completely ignored if the module is just being lexed! If I compile the module as well, I must import mod2, not mod.mod1 in dtest.d. Very confusing.
Then you start getting into multiple modules and modules which import one another. Consider that there is a mod2.d, which does something else, who cares what. Say mod1 privately imports mod2. When all three files are compiled, the modules can be imported just by their name. However, take the modules out of the command line, and suddenly, mod1 can’t import mod2 by just name anymore! You have to actually modify mod1 by changing the import mod2 to import mod.mod2! How is that easy to use?
Protection Attributes
By default, everything is public, and that works fine. Then there are package and private.
Package, to put it simply, doesn’t seem to do ANYTHING. I’ve tried putting modules in so many different directory trees, naming them different ways, compiling them or not, etc. And I can still, very easily, import a module and access its package members. Perhaps it has something to do with the module’s namespace being merged with the current namespace when it is imported? I can’t seem to figure it out.
Private works fine for variables, arrays, and functions, but beyond that, it does nothing. Enums, structs, interfaces, unions, and classes can all be declared private, but it doesn’t stop you from using them outside the module. Note that it’s just definitions that don’t work. if I do this in the module:
private class C
{
int x;
}
C
c;
I will be able to create Cs outside the module, access C’s public static members etc. But I will not be allowed to access the instance “c.”
I posted this as a possible bug, and someone said that perhaps it’s just making the definition private. Well what good does that do? It’s not like we can modify the definition outside of the file, and it would make more sense and be more useful if the entire class were inaccessible outside the module. The same applies for structs, interfaces, unions, and enums.
Conclusion
I really think this mess needs to be cleaned up before
1.0. Modules seem to be a
convenient way to organize your code, but I swear, there are just as many
caveats and irritation as using a header/cpp pair in C++.