On Monday, 2 January 2023 at 22:53:30 UTC, Walter Bright wrote:
> On 12/31/2022 2:28 AM, Max Samukha wrote:
> For types that require runtime construction, initializing to T.init does not result in a constructed object.
The idea is to:
- have construction that cannot fail. This helps avoid things like double-fault exceptions
 
Here, it would help if you clarified what you mean by double-fault exceptions because I just tried to look it up and was lead to tennis and CPU interrupts.
I have a rough idea of what you man and could guess, but I could just ask.
Construction that cannot fail sounds nice, and if an aggregate type constructor can pull it off, it should go for it. But this sounds a lot like nothrow and not something the language should impose. I know people dislike complicated rules because exceptions (to rules, not Exceptions), but a possibility could be that nullary struct constructors must be nothrow or be annotated throw.
> 
- have initializers that can be placed in read only memory
 
I’m not saying that init isn’t a great idea. It’s just that init shouldn’t be used explicitly, but only as “the thing a constructor must act on to produce a valid object”. A “naked” init may be an object that violates its invariants.
An example would be a string optimized for short values (SSO). It has (at least) a pointer to data, a fixed-size internal buffer, and a length with the invariant: pointer = &buffer[0] if and only if length <= buffer.length. A SSO’s init cannot possibly represent the empty string unless we allow pointer to be null to represent it. This means that a SSO has two representations for the empty string. Or we interpret the null data pointer as a null string. In any case, we get something we don’t want.
> 
- have something to set a destroyed object to, in case of dangling references and other bugs
 
If a NaN state is available, use that. (I don’t think NaN states are bad; I actually think that every built-in type except bool should have one: Signed and unsigned integer types could use T.min and T.max. Setting those to 0 is bad because in a lot of contexts, 0 is a perfectly reasonable value, whereas int.min and size_t.max rarely are.
> 
- present to a constructor an already initialized object. This prevents the common C++ problem of adding a field and forgetting to construct it in one of the overloaded constructors, a problem that has plagued me with erratic behavior
 
The problem is, C++ does not complain about you forgetting that field. (For other people:) In C++, if a struct field is of built-in type (e.g. int) and you forget to initialize it, it has an unspecified value. Aggregate types call a nullary constructor and fail to compile if no nullary constructor exists.
Now, even if there is a nullary constructor, it might not be what you want.
Requiring initialization of every field in every constructor is what C++ lacks.
> 
- provide a NaN state. I know many people don't like NaN states, but if one does, the default construction is perfect for implementing one.
 
One question is penalty for the NaN state. Floating-point NaN values are supported by hardware. If we declared int.min and size_t.max as their respective types’ NaN, we’d probably specify existing behavior. The issue is, making them sticky incurs costs.
Floating-point NaN serves two purposes: Indicate an invalid result and error propagation through the program execution. Integer min/max values are used for the former already. People don’t like them do the latter probably.
Another issue of floating-point NaN values is their weird comparison behavior. I understand the argument that x == y should be false if x and y happen to be NaN, but if (x == double.nan) being silently always false feels broken.
> 
- it fits in well with (future) sumtypes, where the default initializer can be the error state.
 
I’m curious what comes out of this.
> An alternative to factory functions is to have a constructor with a dummy argument. Nothing says one has to actually use the parameters to a constructor.
Or we could just allow a nullary struct constructor. It should be backwards compatible (at least to a large degree) if D defines this() @… {} when this() is not defined explicitly (as @disabled or otherwise). An explicit this() should be nothrow if failure is a problem. With throw as an attribute, this() throw { … } is a kind of: “Sorry, Walter, I know you wanted the best for me, but for this type, it’s too wrong to be right.”
That way, a declaration like T x; will call a constructor that – in almost all cases – does nothing, in the remaining cases, in almost all cases does something that cannot fail.
Sorry for the late answer.