woe (noun)

  • great sorrow or distress (often used hyperbolically).

There’s a quote by Bjarne Stroustrup saying that “there are only two kinds of languages: the ones people complain about and the ones nobody uses”. Complaining is therefore a natural result of using a language, and I’ve been using D a bit more in my latest side projects.

While there’s a lot of great things to be said about this language and its tooling (especially when compared to C, C++ and other contenders), there are also some warts here and there. So, here’s some complaining, either pointing out bugs or simply violations of the principle of least astonishment (POLA) in the language (in my opinion, that is).

struct this vs class this

Q: Can you pass this by ref?
A1: Only inside structs, not classes.
A2: Actually, you can assign this to a variable, then pass that variable by ref.
A3: Consider passing by auto ref instead, or maybe in if the argument is also const.

// struct.this vs class.this
// or, pass this by ref

int bar(T)(ref T x) { return 1; }

class C {
    int foo() { return this.bar; } // <--- error
}

struct S {
    int foo() { return this.bar; } // <--- but this works?
}

void main() {
    auto c = new C();
    c.bar; // <--- also works
}

// Error: none of the overloads of template `onlineapp.bar` are callable using argument types `!()(C)`
// Candidate is: `bar(T)(ref T x)`

UFCS vs Method Overloading

Q: Does Universal Function Call Syntax (UFCS) turn variable.foo(arguments) into foo(variable, arguments) ?
A1: Yeah! Cool, huh?
A2: Actually, UFCS only applies to functions in module scope (lambdas or local ones don’t count).
A3: They also don’t work in the presence of methods called foo (never mind the full typed signature).

// UFCS vs Method Overloading

int foo(T)(ref T x, float f){ return 1; }

struct A {}

struct B {
    int foo(bool b) { return 2; }
}

void main() {
    import std.stdio : writeln;

    A a;
    writeln("A * bool  -> ", a.foo(true));
    writeln("A * float -> ", a.foo(1.23)); // <--- ok, uses UFCS

    B b;
    writeln("B * bool -> ", b.foo(true));
    writeln("B * float -> ", b.foo(1.23)); // <--- error (ignores UFCS?)
    writeln("B * float (without UFCS) -> ", foo(b, 1.23)); // <--- ok
}

// Error: function `B.foo(bool b)` is not callable using argument types `(double)`
// cannot pass argument `1.23` of type `double` to parameter `bool b`

taggedPointer vs @safe

Q: Are std.bitmanip.taggedPointers safe?
A1: Yes they’re safe: they only use pointer bits which are known (at compile time!) to be zero due to alignment constraints.
A2: But they’re not scope @safe though. Here’s the bug report.
A3: Also, they don’t work in CTFE because pointer casts are not allowed there.

// taggedPointer accessors are not scope
// Compile with: -dip1000 -main
// Bugzilla #3095, reported in 2022-05-05

import std.bitmanip : taggedPointer;

struct S {
    mixin(taggedPointer!(
        int*, "ptr",
        bool, "flag", 1
    ));
}

void foo(scope ref S s) @safe {
    s.flag; // <--- error
}

// Error: scope variable `s` assigned to non-scope parameter `this` calling `flag`