Thread overview
[Issue 19928] disallow modification of immutable in constructor after calling base ctor
Jun 04, 2019
RazvanN
Dec 17, 2022
Iain Buclaw
June 04, 2019
https://issues.dlang.org/show_bug.cgi?id=19928

RazvanN <razvan.nitu1305@gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |razvan.nitu1305@gmail.com

--- Comment #1 from RazvanN <razvan.nitu1305@gmail.com> ---
I don't think this issue is valid. If we disallow modification of immutable fields after a base class ctor is called then it will be impossible to initialize that field after a super call, which in my opinion is unacceptable behavior. What happens here is that foo is called before the field is actually initialized so it reads the default value of x, then it is called again after x has been initialized. This behavior is correct. The code in the original post is similar to this:

=============================
import std.stdio : writeln;

struct A
{
    immutable int x;
    this(int)
    {
        foo();
        x = 8;
        foo();
    }

    void foo()
    {
        writeln(x);
    }
}

void main()
{
    A a = A(2);
}
=============================

This code compiles and runs successfully. I don't see why it wouldn't. An alternative approach would be to consider that the first use of x locks down the variable and future accesses to it are considered modifications, but this leads to other problems: the constructor will not be able to initialize x and it would require inter-function compilation to determine this; dmd does not support inter-function compilation.

I suggest we close this as invalid.

--
June 04, 2019
https://issues.dlang.org/show_bug.cgi?id=19928

Andrei Alexandrescu <andrei@erdani.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |andrei@erdani.com

--- Comment #2 from Andrei Alexandrescu <andrei@erdani.com> ---
This does seem to be a problem. This is liable to cause problems:

import std.stdio : writeln;

struct A
{
    immutable int x;
    this(int)
    {
        foo();
        x = 8;
        foo();
    }

    void foo()
    {
        passToAnotherThread(&x);
    }
}

The observing thread assumes the immutable(int)* it receives is, well, immutable. In reality that value will change over time.

--
June 04, 2019
https://issues.dlang.org/show_bug.cgi?id=19928

--- Comment #3 from Andrei Alexandrescu <andrei@erdani.com> ---
A solution seems to be disallowing the use of "this" and "this.xyz" (as in passing as an argument to methods or function) until the object is cooked. Cooked means all immutable (or otherwise restricted) fields are initialized.

Implications for other qualifiers and combinations need to be analyzed.

--
June 04, 2019
https://issues.dlang.org/show_bug.cgi?id=19928

--- Comment #4 from Andrei Alexandrescu <andrei@erdani.com> ---
Hm, this does not solve the original problem though.

--
June 04, 2019
https://issues.dlang.org/show_bug.cgi?id=19928

--- Comment #5 from Andrei Alexandrescu <andrei@erdani.com> ---
So a solution to the super() issue would be to require the immutable fields are
assigned before calling super(). It is odd but it works.

--
June 04, 2019
https://issues.dlang.org/show_bug.cgi?id=19928

Steven Schveighoffer <schveiguy@yahoo.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 OS|Mac OS X                    |All

--- Comment #6 from Steven Schveighoffer <schveiguy@yahoo.com> ---
The reason you can modify immutable in a constructor is because nothing else has access to it. As soon as you give access to it elsewhere, it can cause a problem. Andrei is correct, calling any function which allows access to the immutable provides a vector of breaking immutability.

(In reply to RazvanN from comment #1)
> I don't think this issue is valid. If we disallow modification of immutable fields after a base class ctor is called then it will be impossible to initialize that field after a super call, which in my opinion is unacceptable behavior.

It's tricky, and definitely difficult to deal with. It would disallow certain solutions that seem otherwise valid. But that might be the cost of having a correct immutable implementation.

I will note that Swift enforces all members are initialized (look for 2-phase initialization) before calling a super ctor: https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

Java is the opposite, it requires you call any super ctor FIRST before
initializing members:
https://stackoverflow.com/questions/15682457/initialize-field-before-super-constructor-runs

But they also don't have a concept of immutable which is implicitly sharable.

--
June 04, 2019
https://issues.dlang.org/show_bug.cgi?id=19928

--- Comment #7 from Andrei Alexandrescu <andrei@erdani.com> ---
I'm glad Swift provides a precedent and inspiration. We should follow their model.

--
December 17, 2022
https://issues.dlang.org/show_bug.cgi?id=19928

Iain Buclaw <ibuclaw@gdcproject.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Priority|P1                          |P4

--