On Monday, August 4, 2025 2:02:48 PM Mountain Daylight Time Brother Bill via Digitalmars-d-learn wrote: > I feel like I am going into the hornet's nest with this > discussion. > > I have created a struct with some members, and want to have a > parameterless constructor that sets the member values at run time. > > I have seen things like @disable this(); and static opCall(), > but don't quite understand them. > > How does one do this, and what are the ramifications of doing so? > Or should one just leave the parameterless constructor as is?
Realistically, you really shouldn't ever do this in D. D as a whole is designed with the idea that all types can be default-initialized. It simplifies various aspects of the language and helps make it memory-safe. If a struct is used somewhere where it's default-initialized rather than explicitly constructed, it starts with that struct's init value - that is what each of the member variables are directly initialized with (and all of that is calculated at compile time). This avoids issues with garbage values like you get in C/C++. And if a value is explicitly constructed, then the struct is initialized to its init value before the constructor is called, ensuring that the constructor isn't dealing with garbage either. On the whole, this has simplified the language, but it does come with the downside that default construction doesn't exist in D. So, you can't just declare a variable and expect it to run any code. You need to explicitly call a function (be it a constructor or some function that returns that type) to be able to run code when initializing a variable, e.g. auto s = S(42); or auto s = foo(); So, at its core, what D does with default initialization is pretty simple. You get the init value. And D takes advantage of this in a number of places (e.g. declaring a dynamic array of a given size will initialize all of its elements to the element type's init value). Unfortunately, the addition of the ability to disable default initialization and the ability to have nested structs has complicated things a bit, and not in a good way. And adding static opCall into the mix just makes things worse. To quickly explain @disable this();, what it does is make it so that that type cannot be used in any context where it's default-initialized. That means that S s; won't compile, and neither will auto arr = new S[](10); The struct can still be explicitly initialized with its init value, e.g. auto s = S.init; but the idea is that at that point, it really should just be initialized using a constructor - presumably because the programmer wanted to require that some piece of code be run when the struct is initialized. To quickly explain opCall, it's the overloaded operator which lets you call a variable like it's a function, e.g. S s; s(42); And like any function, it can have zero or more arguments, depending on the parameters that the function has. static opCall is then the overloaded operator which lets you call a type like it's a function, e.g. auto s = S(42); This is mostly a terrible idea, because you can just use a constructor for that, and while you _can_ make static opCall return a different type entirely, that's just going to be really confusing, because everyone is going to expect S(42) to be calling a constructor to construct an S. However, some folks like to use static opCall to try to act like a struct has a default constructor. Normally, auto s = S(); would default-initialize a type, similar to auto s = S.init; but if S has a static opCall which takes no arguments, then it would call that static opCall. But it's still not a default constructor, because something like S s; is not going to call that static opCall. It's just going to default-initialize the variable. So, trying to use it as a default constructor is going to be error-prone. Some folks have then tried to combine that with disabling default-initialization (i.e. @disable this();) so that it's then not legal to do S s; so that your static opCall can't be bypassed with default initialization, but you still have to explicitly call S(), and of course, it means that you can't use S in any situation which requires default initialization (which can get annoying, because various parts of the language were designed with the assumption that all types can be default-initialized, and thus they either don't work at all with such types that don't allow it, or they only partially work with such types). The situation gets even worse though, because some generic code which wants to use the default-initalized value for a type will use S(), e.g. foo(S()); instead of foo(S.init); This is for two reasons: 1. It will fail to compile if S can't be default-initialized, whereas auto s = S.init; would compile just fine even if S has @disable this();. So, if you don't want to bypass @disable this();, you need to not use the type's init value. 2. Nested structs have a context pointer (to access their outer scope) which only gets a proper value if the struct is default-initialized. And while historically, default initialization meant that a type was initialized with its init value, for nested structs, that's not true. They get initialized with their init value, and then their context pointer is initialized based on where they're defined. So, any code which uses the init value rather than using default initialization will end up with a nested struct whose context pointer is null, and if it ever tries to use its context pointer, then you'll get a segfault. Using S() rather than S.init avoids the problem, because S() does default initialization, whereas S.init is just the init value. So, this means that there is D code out there which explicitly uses S() for default initialization, and if you declare a static opCall for S which takes no arguments, then it's going to have problems if it's ever used with such code. So, unfortunately the fact that nested structs and the ability to disable default initialization were added to the language has complicated things somewhat. But at the end of the day, D is designed around the idea that all types can be default-initialized, and trying to fight that means that you're going to be fighting warts in the language. Disabling default initialization will make a variety of language features not work with your type, and using static opCall is a bug waiting to happen. It may work just fine if you're in control of all of the code that you're using, but if you call into library code anywhere with your type, you could easily run into trouble. And really, if you want a factory function, you can just give it a different name, e.g. struct S { ... static S make() { ... } ... } Using static opCall is really just syntactic sugar that risks causing you problems. It doesn't actually buy you anything. Folks just like using it, because it looks like a default constructor even though it isn't. And if you actually _need_ to disable default initialization for a struct in order for it to work properly, then you can do it, but be prepared for various language features (like dynamic arrays) to not work very will with your struct. So, at the end of the day, I'd strongly advise against trying to have structs with anything like a parameterless constructor. There may be cases where it still makes sense, but you'll be fighting the language pretty much any time that you try it. As general rule, it really only makes sense in very restricted circumstances, and even then, it's better to use a factory function with an actual name. - Jonathan M Davis