Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Namespaces

In C++, there is a concept called a "namespace." Namespaces in C++ were largely introduced to counteract the problem of name conflicts when importing large bodies of code. Because of this rather kludgy purpose, explicit namespaces do not exist in D, for the reason that modules create enough of a namespace.

While the C++ idea of a namespace is indeed not useful in a language like D, it can be recast into a new language construct, one which follows the same basic idea, but which provides useful features and abstraction capabilities.

MiniD has namespaces, but unlike in C++, they are meant to logically group together declarations, and with a little syntactic sugar, can represent some other interesting constructs as well.

Creating Namespaces

Namespaces can exist in two places: at module level and inside class declarations. They follow the same form, but only differ in what kinds of declarations they can hold.

DeclDef:
	ClassDeclaration
	ModuleNamespaceDeclaration
	Declaration

ClassDeclaration:
	'class' Identifier [':' Identifier {'.' Identifier}] '{' {ClassElem} '}'

ClassElem:
	CtorDeclaration
	DtorDeclaration
	ClassNamespaceDeclaration
	Declaration
	
CtorDeclaration:
	'this' Parameters FunctionBody

DtorDeclaration:
	'~' 'this' '(' ')' FunctionBody
	
ClassNamespaceDeclaration:
	'namespace' Identifier '{' {ClassNamespaceDeclaration | Declaration} '}'

ModuleNamespaceDeclaration:
	'namespace' Identifier '{' {DeclDef} '}'

Declaration:
	VariableDeclaration
	FunctionDeclaration

As this snippet of the grammar shows, a module-level namespace can hold any kind of declaration, while a class-level namespace can only hold regular (variable and function) declarations and other namespaces (which have the same constraint). This is so that classes cannot be declared inside other classes (a restriction which could be changed in MiniD 2.0 ;) ).

Here are some examples of namespaces:

namespace Utensils
{
	def void fork()
	{
		io.writefln("fork!");
	}
	
	def void knife()
	{
		io.writefln("knife!");
	}
}

class MyClass
{
	namespace Position
	{
		def int mX;
		def int mY;

		def int getX()
		{
			return mX;
		}
	}
}

Here is how they'd be used:

Utensils.fork();
Utensils.knife();

def MyClass m = new MyClass();
m.Position.mX = 5;
io.writefln(m.Position.getX());

You access their members them with the dot syntax, just like class or module members.

Namespaces are not a "true" language construct; that is, they mean nothing in the outputted program. All they do is add a prefix to a name. Because of this, namespaced class members can also be overridden:

class Base
{
	namespace position
	{
		def int getX()
		{
			return 5;
		}
	}
}

class Derived : Base
{
	namespace position
	{
		def int getX()
		{
			return 10;
		}
	}
}

...

def Base b = new Derived();
io.writefln(b.position.getX()); // writes 10, as position.getX() has been overridden

Okay, this is all well and good; we can logically group declarations. But what other purposes do namespaces serve? Well, namespaces can also participate in operator overloading, just as classes can. Because of this, interesting things can be achieved:

class OpTest
{
	namespace array
	{
		char[] opIndex(int index)
		{
			return string.format("index: ", index);
		}
		
		char[] opIndexAssign(char[] value, int index)
		{
			return string.format("index: ", index, " value: ", value);
		}
		
		int length()
		{
			return 5;
		}
	}
}

...

def OpTest t = new OpTest();
io.writefln(t.array[4]); // writes "index: 4"
io.writefln(t.array[5] = "hey"); // writes "index: 5 value: hey"
io.writefln(t.array.length()); // writes "5"

So namespaces can be used to imitate other language constructs, such as arrays. But look at that last function, length() - It would be nice to be able to write:

io.writefln(t.array.length);

This is a property, and MiniD supplies a way to do this.

Using Namespaces as Properties

In D, properties are nothing more than an odd bit of syntactic sugar for calling functions; thus, "obj.length = 5" becomes "obj.length(5)". The problem with this method is that it loses some expressivity; since the compiler doesn't know anything about "properties", it can't figure out how to do something like "obj.length += 4". MiniD solves this by using namespaces and operator overloading.

There are two special operator names which can be defined in namespaces, and they are opSet and opGet. These are used to implement set and get semantics for properties. Here is an example of how to create a property using a namespace:

namespace prop
{
	def int mValue; // "internal" value

	def int opSet(int val)
	{
		return mValue = val;
	}
	
	def int opGet()
	{
		return mValue;
	}
}

prop = 5;
io.writefln(prop); // writes 5
prop += 6;
--prop;
io.writefln(prop); // writes 10

Very cool. Because the compiler knows that namespaces can be used as properties, it can implement the semantics for things like -- and +=.

As for the semantics of operation assignments, such as +=: in this example, the expression "prop += 6" is rewritten as "prop.opSet(prop.opGet() + 6)". However, since namespaces can overload opAddEq as well, the compiler first looks for a matching op__Eq method; failing that, it then looks for opSet and opGet. This only applies to operation assignments (and increment and decrement, since they are rewritten as operation assignments).

Namespaces can define multiple setters as well:

namespace prop
{
	def int mX; // internal value

	def int opSet(int value)
	{
		return mX = value;
	}
	
	// allow the property to be set with a float
	def int opSet(float value)
	{
		return mX = cast(int)value;
	}
	
	// allow it to be set with a string
	def int opSet(char[] value)
	{
		return mX = string.toInt(value);
	}

	// multiple parameters with a default value
	def int opSet(bool val, bool display = false)
	{
		if(display)
			io.writefln("Setting x to ", cast(int)val);
			
		return x = cast(int)val;
	}

	def int opGet()
	{
		return mX;
	}
}

...

prop = 5; // calls the opSet(int) overload
prop = 3.1415; // calls the opSet(float) overload
prop = "45"; // calls the opSet(char[]) overload
prop = true; // calls the opSet(bool, bool) overload, with the first parameter as "true" and the second "false"
prop.opSet(true, true); // calls the opSet(bool, bool) overload

Most of the use cases are obvious. The two interesting cases are the last two, which use the opSet overload with two parameters.

If all but the first parameter of an opSet has a default value, it can be set as a regular property using "=". However, to explicitly pass more than one parameter to a setter, the explicit ".opSet()" syntax must be used. This is because something like

prop(true, true);

Would not look for an opSet overload, but rather, an opCall overload.

Lastly, since opSet and opGet are regular functions, they can be referenced just like any other function:

def void call(int function() func)
{
	io.writefln("call got: ", func());
}

prop = 4;
call(prop.opGet); // writes "call got: 4"