Did you know about D anonymous classes?

Posted 2021-02-15

I use D's anonymous classes and even I didn't know how to pass arguments to their constructors until this week!

Core D Development Statistics

In the community

Community announcements

See more at the announce forum.

Tip of the Week

D has anonymous classes, similarly to Java. You can use return new class {...} to make one on the spot:

class A {}

A foo() {
	return new class A {
		override string toString() { return "anonymous!"; }
	};
}

You don't even need to give the base class name since it will default to Object:

Object foo() {
	return new class {
		// stuff
	};
}

You can even implement a parent and some interfaces:

interface I { int getInt(); }
class Base { string getString() { return "string"; } }

I foo() {
	return new class Base, I {
		int getInt() { return 15; }
	};
}

void main() {
	import std.stdio;
	I i = foo();
	writeln(i.getInt());
	Base b = cast(Base) i;
	writeln(b.getString());
}

Pretty cool, can sometimes save you a line or two of writing a separate class definition. OK, not that big of a difference since you can nest a class in a function too and access local variables all the same, but it can still be nice.

In fact, a kinda cool thing is you can combine this with D's auto return and actually have a reference to the anonymous type itself instead of just the base / interface:

auto foo() {
        return new class {
                int i;
        };
}

Why is that cool? Because you can use D's reflection on it!

auto foo() {
        return new class {
                int i;
        };
}

void main() {
        auto f = foo();
	// shows the `i` and the auto-generated `this` ctor:
        pragma(msg, __traits(derivedMembers, typeof(f)));
}

Nowhere else does D allow declarations in an expression context so an anonymous class might be a convenient grouping for a single-use thing to be passed for reflection too... but the spec doesn't allow class objects to be sent as template value args, so you'd have to work with it strictly on the CTFE side or strictly as a type.

template some_cool_thing(T) {
pragma(msg, __traits(derivedMembers, T));
}

void main() {
some_cool_thing!(typeof(new class {
	int y;
}));
}

That typeof(new class { }) is legal and lets you group some declarations for template use without a separate name... but is it useful? Well... maybe! I'm gonna keep this in my quiver just in case it is useful some day, but it is a bit verbose so not that compelling vs just writing the declaration separately.

Anyway, using it in a function as a quick anonymous grouping doesn't need anything that fancy and it might be an amusing alternative to tuples. But it is of course a class allocation. You can do a scope i = new class {}; for a local variable which doesn't allocate:

void main() @nogc {
        scope a = new class {
                int a = 5;
        };

        import core.stdc.stdio;
        printf("%d\n", a.a);
}

But of course you can't return that so it can only be used as a local variable.

fun fact: dmd -betterC accepts that function, but it fails to link due to missing druntime support. This is arguably a bug in betterC's rejection detection but since I hate betterC's rejections I'm not gonna file it.

However, speaking of betterC, this little program works even there!

extern(C++) class O {
        extern(D)
        abstract int omg();
}

extern(C)
int main() @nogc {
        scope a = new class O {
                int a = 5;
                override int omg() { return a; }
        };

        import core.stdc.stdio;
        printf("%d\n", a.a);

        return 0;
}

dmd -betterC works. I used anonymous classes recently for a COM implementation and it wasn't too bad, so the fact you can do that with betterC is legit kinda cool and likely kinda useful.

The class' scope overrides the local scope!
class A {
	int a;
}
A foo() {
	int a;
	return new class A {
		this() {
			/* in here, a refers to A.a,
			not the local variable `a`
			from directly above! */

			// this, this is a self-assign
			this.a = a; // does nothing
		}
	};
}

This makes sense under language rules, arguably the member variable from the parent class is "closer" to that constructor, but it is easy to forget in the moment and the compiler will not warn you about the do-nothing self assignment.

So those are all cool facts, but the warning box above is written out of experience. How can you pass that local variable in? Normally, you'd pass it to a constructor, but can anonymous classes have constructor arguments?

I thought no. But after reviewing the Java spec and finding them there, I took a second look at the D spec.... and found them here too. This is the part I didn't know until this week! The syntax is wild though. Behold, the majesty:

void main() {
        int a = 83;
        auto obj = new class (a) { // that (a) is the arg list to the ctor!
                this(int a) {
                        import std.stdio;
                        writeln("this a was passed in ", a);
                }
        };
}

Yes, the argument list for the constructor goes after the class keyword, before the base class and interfaces.

Technically the spec also allows a list of allocator arguments after the new keyword too btw, but that's for like placement new and not related.

Just anyway like after the class keyword is the last place I would have guessed. Normally you write new Object(args) so I tried new class Base(args).. that's what works in Java, but no luck in D. new class { ... } (args);, no luck.

Only by consulting the grammar in the spec did I come to this. I guess it kinda sorta does make sense. The args go after the class name, the class name goes after the class keyword, and since this class has no name it kinda makes sense that the args are where they are. And since D has templates, putting it in the same place as Java might look weird (but then again the template instantiation requires the ! operator so like meh?).

Well, whatever, the fact is that you can do it! And this is how you can pass in local vars without worrying about name clashes between base class members and those local vars.

Bonus tip!

Did you know you can put UDAs on a module declaration?

@foo module whatever;

import module_with_foo;

The imports are not allowed to be in the file before the module declaration, but the symbols from later imports are nevertheless available.

Only UDAs and deprecated are currently allowed on module declarations.

I didn't know this until someone pointed it out to me in the spec on chat.

What Adam is working on

simpledisplay.d is getting drag and drop support soon. It should be included in the arsd tag 9.2 in the next week or so.

I might also make a --book-mode for adrdox that sorts articles in a chapter-like format. But that is likely to be a little while.