| Posted by Jonathan M Davis in reply to IGotD- | PermalinkReply |
|
Jonathan M Davis
Posted in reply to IGotD-
| On Monday, October 23, 2023 12:54:45 PM MDT IGotD- via Digitalmars-d wrote:
> I've recently seen that modern program languages are going away from default initialization. D early recognized no initialization being one of the many flaws in C/C++ and added default initialization which greatly improved stability which has been the standard for many languages since. C++ "fixed" this very recently but really not.
>
> Now even more modern languages are going away from default initialization as some claim that default initialization was a source of bugs. The implementation might differ, some must give a variable a value at declaration others detect use before assign.
>
> What is your take on this and if D would ever be modernized, is this something that D should consider?
Default initialization is too baked into D for it to change at this point, and there are features that we have which you can't have without it (at least not without having objects being garbage, because they haven't been given a value yet). It's why disabling default initialization on structs can be quite annoying, since it results in them not working in a number of places (particularly with regards to arrays).
In general though, if you want to avoid issues with objects being used before they're properly initialized, you have two options:
1. Have default initialization so that it's guaranteed that the object will have a value even if it's ever used before it has the value that you want it to actually have.
2. Make the compiler smart enough to catch when a variable is used before it's initialized and make it an error, thereby forcing explicit initialization of all variables before they're used.
The second approach works in a number of cases, but inevitably, the compiler is not smart enough to correctly determine whether a variable has actually been initialized in all cases, forcing you to give it a value when it shouldn't be necessary (this is something that can happen in Java quite easily). And it's _not_ something that is actually fully solvable, because it's possible that whether a variable has been initialized yet or not depends on runtime logic which is affected by stuff outside of that function, which the programmer is aware of, but the compiler can't be due to separate compilation (though arguably, if the logic is separated enough from the variable's initialization, it should be given a value regardless just to be safe, since the other code could change without the variable's initialization code being altered appropriately).
It becomes easier for the compiler if you simply require that the variable be given a value when it's declared, but that arguably just makes the problem worse, because then you're forced to give the variable a value before you want to in any situation where you need to declare it before giving it the actual value that you want to give it. And ultimately, it results in the programmer manually doing what default initialization would have done and give the variable a value that they don't actually want to use but which will at least not be garbage.
However, arguably, an even bigger issue is initialization that doesn't necessarily involve a variable. For instance, you could have something like
auto arr = new int[](10);
or
foo(new int[](10));
In D, all of the array's elements are default-initialized to the init value of the element type, so no garbage is involved, but what if you had no default initialization? You'd be forced to do something like
auto arr = new int[](10, value);
and set all of the elements in the array to some programmer-provided value,
which really isn't any better than using a default value and might even
be less efficient given that the compiler knows the type's init value at
compile time, but it wouldn't know what the value would be if it's provided
at runtime.
Various other aspects of arrays also depend on the default value and really can't be done without it. E.G.
arr.length += 20;
isn't going to work without a default value. Without that, you'd be forced to append or use a different syntax that provided your own value to use as the default before you go and assign it what you actually want it to be later.
Default values also make implementing the logic in user-defined types easier and safer (especially in classes with inheritance), because the compiler can rely on everything having an actual value before the constructors run. Having something like immutable does actually force you to implement a lot of the same logic that you'd have to have without default initialization, so D can't simplify how constructors work with regards to code flow analysis quite as much as might be nice, but a language without immutable or const could forgo a lot of the code flow analysis that languages typically have to do for constructors in the face of inheritance, because it could know that the member variables wouldn't be garbage. With D, the compiler knows that they're not garbage but has to make sure that it doesn't reassign values to any const or immutable member variables.
There are certainly cases where default-initialized objects can be a problem (e.g. when you end up with a null pointer or reference), and in some cases, having default values can be problematic (which is why the ability disable default initialization was added to structs in D), so there's certainly an argument for requiring initialization in some cases rather than relying on a default value, but in general, having default values for all types is far more flexible than requiring that the compiler track whether a variable is initialized before it's used. So, I think that D made the right choice, but it's not particularly surprising if a number of the newer languages out there have made a different choice.
Ultimately though, I don't think that it's possible to prevent bugs with regards to initializing variables with default values that shouldn't actually be used, because even if you don't have default values in the language, there will always be cases where programmers have to give a variable a default value of some kind anyway, because the compiler can't always correctly determine whether a variable is used before it's initialized if it allows deferring initialization (and if it doesn't allow defering initialization, then you'll end up with user-decided default values that much more frequently).
- Jonathan M Davis
|