| |
 | Posted by Jonathan M Davis in reply to Brother Bill | Permalink Reply |
|
Jonathan M Davis 
Posted in reply to Brother Bill
| On Tuesday, July 29, 2025 5:18:18 PM Mountain Daylight Time Brother Bill via Digitalmars-d-learn wrote:
> Your changes do work, but at the cost of making i NOT deeply immutable: no assignment, no changing any elements, no appending, no setting length. This syntax permits appending and setting length, with no assignment and no changing any elements.
>
> The idea of shared static this() is that is has "special" privileges to mutate immutable slices and other references. These run prior to main, so it is "safe" to make mutations at this early point.
>
> I would like to execute the original code.
The original code is not supposed to work. It's a violation of the type system to mutate const or immutable data, and allowing the mutation of a const or immutable variable at any point - even in a constructor - is a hole in the type system which can cause subtle bugs. A const or immutable variable is initialized with a value (either by default initialization or by explicitly giving it a value), but it can't be mutated after that.
What's special about a shared static constructor is that it's allowed to give an immutable module-level variable its initial value instead of requiring that the variable be initialized directly with a value via CTFE (compile time function evaluation), since directly initializing a module-level variable would be done with a value that's generated at compile time. So, instead of doing something like
immutable int[] i = [foo(), bar()];
the shared static constructor makes it possible to do the initialization at runtime, e.g.
immutable int[] i;
shared static this()
{
immutable(int)[] temp;
temp ~= foo();
temp ~= bar();
i = temp;
}
But even the shared static constructor isn't allowed to mutate the value of i. It's allowed to initialize it exactly once. This is the same with constructors for user-defined types, e.g.
class C
{
immutable int[] arr;
this()
{
arr ~= 42;
}
}
will fail to compile, because it's attempting to mutate the immutable member variable, whereas something like
class C
{
immutable int[] arr;
this()
{
immutable(int)[] temp;
temp ~= 42;
temp ~= 57;
arr = temp;
}
}
would be legal.
There have been bugs with initializing const and immutable variables in constructors in the past which made it possible to mutate const or immutable variables, and the language has been tightened to fix such holes. Any more holes that exist will hopefully be found and fixed, but it's very much purposeful that your example code does not compile.
This is part of why copy constructors were introduced to replace postblit constructors (though that's still a work in progress with regards to dynamic arrays and AAs, since the work to fully implement support for copy constructors for them is still being worked on). Postblit constructors were designed with the idea that the constructor would first copy the struct and then modify any member variables which needed modification afterwards (e.g. by doing a deep copy) instead of requiring that each member be explicitly initialized, but making that work required temporarily treating const and immutable data as if it were mutable, and that could result in it being mutated, which violates the guarantees that the type system is supposed to provide with const and immutable.
So, work has been done to make it so that D no longer has anywhere where it allows a constructor to mutate const or immutable variables. Constructors can initialize such variables, but the variables aren't allowed to be mutated.
If you want to be able to have a mutable variable that you then use to
initialize an immutable variable, then that mutable variable needs to be a
separate variable that is then used to give the immutable variable its
value. For types with no indirections, this can typically be done
simply by initializing the immutable variable with the mutable variable,
because the value is simply copied, but for types with indirections, either
a deep copy must be made, or it must be cast to immutable (which is @system,
because it will violate the type system if any of that data is then mutated
via a mutable reference after that).
So, you could do something like
shared static this()
{
int[] temp;
temp ~= foo();
temp ~= bar();
i = temp.idup;
}
or
shared static this()
{
int[] temp;
temp ~= foo();
temp ~= bar();
i = cast(immutable) temp; // @system
}
if you wanted operate on a fully mutable array, but the type system is supposed to guarantee that immutable data is never mutated, so the compiler does not allow you to mutate an immutable variable even in a constructor. It just allows you to initialize it.
- Jonathan M Davis
|