Structs are Not Plain: A call for empty struct constructors
FeepingCreature
feepingcreature at gmail.com
Thu Sep 19 09:02:39 UTC 2019
Let me lay out the problem first.
We're doing unittesting with fixtures. A Fixture is a struct that
contains default values and mocks of the class structure that is
being tested. Unittests will have the form
```
unittest {
with (Fixture()) {
}
}
```
so that within the unittest the fields of the fixture can be
easily accessed.
Now, from this follow a few requirements. First, Fixture *has* to
be a struct. Why "has"? Because one thing the mocker does is
check if its expectations were fulfilled, and it does that at
scope exit with `~this()`. So the mocker has to be a struct - so
Fixture has to be a struct, because only structs have a halfway
cleanly defined end of lifecycle.
(`scope(exit) validate;` does *not* work - that's the sort of
line you just know will get forgotten. We want validation to be
automatic.)
So how to construct a Fixture? It can't be the `init` state,
because there's classes in it. It can't be a constructor, because
D structs cannot have empty constructors (because "why would you
want that if they're just plain old data"). So we have to use
`static Fixture opCall()`, aka "ssh, it's a constructor but we'll
pretend it isn't." But that means we can't use a regular
autogenerated constructor, because if we give the struct *any*
constructor it disables `static opCall`.
(Why? I can only conclude that we have sinned by giving the
struct a fake empty constructor, and the language has decided to
punish us.)
What else follows from this? We can't use immutable - because
immutable fields can only be set in a proper constructor. We
could do horrible workarounds like `Fixture(null)`, but that's
just making a bad situation worse.
So that's all terrible.
I think the deeper problem stems right from the original
assumption that structs are "plain old data" and thus shouldn't
be interested in construction. With their use in reference
counting, with(), destructors, assignments... does anyone still
really believe that? Structs have taken on a dual role of "plain
old data, and also anything that needs customized behavior bound
to lifecycle."
The decision to not allow `this()` stems from a time where that
wasn't really on the table, and I think it should be overturned.
Let `Struct s;` be Struct.init, by all means, but `Struct()`
should call `this()`, if there is one.
More information about the Digitalmars-d
mailing list