enum arrays probably aren't what you want

Not "an array of enums", but a manifest constant that is an array.

Consider:

enum a1 = "hello";
enum a2 = ['h', 'e', 'l', 'l', 'o'];
enum a3 = [ "greeting": "hello" ];

bool same(T)(T a, T b, T c) {
    return (cast(void*) a) == (cast(void*) b)
        && (cast(void*) b) == (cast(void*) c);
}

unittest { // these all succeed
    assert(same(a1, a1, a1));
    assert(!same(a2, a2, a2));
    assert(!same(a3, a3, a3));
}

An enum, as a manifest constant, is essentially copy-and-pasted into your program where the constant is seen, similarly to a #define in C. For string literals this doesn't matter, but for other array literals (even when the type is string, like enum string a2 = [...]) D is allocating a completely fresh data structure everywhere you see the constant. Therefore, the three a2 and a3 enums have completely different pointers.

It really is possible to want this outcome for other array literals, say when you're passing a mutable array to a function (kinda the opposite case of Python default arguments sharing mutable array default values). But when you just want an efficient constant, the other kind of #define, you can use immutable or static const:

enum a1 = "hello";
immutable a2 = ['h', 'e', 'l', 'l', 'o'];
const a3 = ['w', 'o', 'r', 'l', 'd'];

// AAs need runtime initialization
const string[string] a4;
shared static this() {
    a4 = ["greeting": "hello"];
}

bool same(T)(T a, T b, T c) {
    return (cast(void*) a) == (cast(void*) b)
        && (cast(void*) b) == (cast(void*) c);
}

const(string)[] f() {
    static const a5 = ["not", "toplevel", "so", "needs", "static"];
    return a5;
}

const(string)[] g() {
    const a6 = ["oops"];
    return a6;
}

unittest {
    assert(same(a1, a1, a1));
    assert(same(a2, a2, a2));
    assert(same(a3, a3, a3));
    assert(same(a4, a4, a4));
    assert(same(f(), f(), f()));
    assert(!same(g(), g(), g()));
}