Understanding mixin templates, terminal.d improvements

Posted 2020-01-20

Core D Development Statistics

In the community

Community announcements

See more at the announce forum.

terminal.d improvements

I did some work on terminal.d last week, adding support for tmux TERM (it worked before, you just had to set TERM=xterm, but now it should just work) and fixing various little things with the getline function: it now handles alt+nnnn input on Windows correctly, does ctrl+arrow movements (on terminals that send the necessary code - except rxvt, it is different and I haven't decoded that yet), and a few other little bugs and edge cases.

I am looking at making a terminal.d program transparently be able to extend itself with a gui as well, perhaps by making my terminal emulator a possibly dependent package so it will just work. More to come next week probably.

Understanding mixin templates

D's mixin templates are a very interesting beast. Let me start off with a quiz. How big is struct A here?

mixin template Foo() {
        int x;
}

struct A {
        mixin Foo a;
        mixin Foo b;
}

If you said four bytes, that is understandable... you are probably thinking of a mixin template as a series of declarations that are copy/pasted into the new context. However, that's wrong! (If Foo were a string mixin, you'd be right! Same keyword, totally different things.)

The answer is eight bytes.

Same with this:

mixin template Foo() {
        int x;
}

struct A {
        mixin Foo a;
        int x;
}

That also has size 8. (And if you did just mixin Foo; it would still be 8! Though you'd be unable to access the value.)

A mixin template is basically an aggregate of their own that exposes names; think of them as a struct that magically forwards name access from the top level.

Each time you mixin a mixin template, you get another copy of the data, just like if you had two struct member variables.

This is actually quite useful because it means you can make mixin templates that have private state. I used this in jni.d to cache method IDs from Java - the mixin template brought in the wrapper method and the cache variable into each instance of the class (which a static local variable cannot replicate!).

The key difference between a mixin template and a struct is in scoping. With a struct, names go up from the definition point, through the defining module. With a mixin template, names start from the definition point, then go up through the usage point.

This can be frustrating because your imports will likely have to be local to the mixin template definition or you get errors.... but it can also be useful because it lets the user "inject" new stuff to the existing definition.

I most often use this through the this keyword. In fact, it is sometimes necessary to get what you want! Take a look:

1 interface I {
2         string name();
3         string somethingElse();
4 }
5 
6 mixin template IDefaults() {
7         override string name() { return typeof(this).stringof; }
8         // this is actually going to always return the default...
9         override string somethingElse() { return name(); }
10 }
11 
12 class C : I {
13         mixin IDefaults;
14 }
15 
16 class D : C {
17         mixin IDefaults;
18         override string name() { return "overridden"; }
19 }
20 
21 void main() {
22         import std.stdio;
23         D d = new D;
24         writeln(d.name());
25         writeln(d.somethingElse());
26 }

The first write will say "overridden", but the second one will still say D. The look up of somethingElse starts in the mixin template itself... and in this case, stops there. The IDefaults.name is its own thing, independent of the usage points.

To explicitly want the outside one... it means looking outside the mixin template. This is where the "injection" comes in, the fact that it looks up at the usage point. There is no this symbol inside a mixin template (unlike a struct), so if I were to write this.name in there, it'd look up this at the usage point and thus consider the externally overridden names instead of just the internal one.

It gets a bit confusing because the internal one can actually be virtual and overridden externally that way. If you try to mixin two implementations, like

class C : I {
        mixin IDefaults a;
        mixin IDefaults b;
}

then you will get a sea of error messages. The compiler knows there are two separate IDefaults things... but doesn't know which one is supposed to be used for the interface.

You'd fix this by writing out the functions on the outside and referring to the other function like return a.name(); or whatever.

While all these rules can get weird and dealing with overloads across them, etc., can be frustrating, leading many to use string mixins instead, they can be used for some pretty cool stuff. Separate private state means you can mixin caches or static variables (and when used with static foreach it becomes fairly mindless for the user!). Selective overriding means you can provide a bunch of defaults and let the user still do their own thing for individual functions. And the scoping at usage point means you can inject other code while reusing the rest. And, of course, you can add static constructors for initialization at the usage point defined in the library.

It really becomes a "mix" in :)