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