First Draft: Static Single Assignment

Jonathan M Davis newsgroup.d at jmdavisprog.com
Thu Nov 27 19:41:18 UTC 2025


On Saturday, November 15, 2025 12:13:13 AM Mountain Standard Time Walter Bright via dip.development wrote:
> https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.html

Okay, what's the real motivation here? If it's to prevent assignment, then
IMHO, that's what the DIP needs to say. It should not talk about preventing
"modification" or "mutation." If that's what you want, then just use const.
So, assignment needs to be called out as illegal rather than talking about
mutation or modification being illegal.

Of course, that also leaves the question of "op assignment" (e.g. +=, -=,
etc.), since on the one hand, that's arguably assignment, but on the other
hand, it's not really any different from calling a member function which
mutates a member variable (and if that's on the list of things to prevent,
you might as well just use const). A struct or class could just as easily
have defined an add function instead of opOpAssign!"+", but it's likely to
have defined opOpAssign!"+" in order to get the nice syntax, so I'm inclined
to argue that those operations should be allowed with user-defined types.
And if they're allowed with user-defined types, then they really should be
allowed with primitive types, or the result would be inconsistent. And in
that case,

final int i = 42;
i += 10;

should be legal, whereas

final int i = 42;
i = 52;

should be illegal. And if the desire is to prevent operations like += as
well, then const or immutable should be used. If that's not the case, then
we're going to have a very weird situation with user-defined types.

Of course, the other issue here is that because final is a storage class and
not a type qualifier, things are going to degrade to const pretty quickly
with pointers and references. For instance,

final int i = 42;
auto ptr = &i;

is going to have to make ptr const(int)* or const(int*) if we want to
prevent being able to assign i a new value indirectly - which the DIP does
discuss, though it doesn't say what happens with auto. It just talks about

final int i = 42;
int* ptr = &i;

being illegal, whereas

final int i = 42;
const int* ptr = &i;

would be legal.

But to handle that cleanly in more complex cases, you'd really need final to
be a proper type qualifier, which would mean something like

final int i = 42;
final(int)* ptr = &i;

since without that, it all degrades to const, significantly reducing the
utility of final in such code, but making final a type qualifier also opens
a very large can of worms which we probably don't want to open.

But honestly, having final be a storage class only starts making this feel
really hacky once you're dealing with stuff like taking the address of the
variable or passing it by ref. That's probably better than actually making
it a type qualifier, because that would be a huge complication for what
seems like a niche feature, but I'm very worried that this is going to get
into some of the same mess that we currently have with scope where we have
all kinds of weird corner cases, because scope is non-transitive and is a
storage class rather than being a proper type qualifier.

Of course, personally, I don't think that this feature adds enough value to
be worth adding at all, but it does feel very much like something being
welded onto the language to solve a corner case that doesn't work with const
rather than having a more holistic solution.

In any case, I really think that the DIP needs to be very clear about how
it's about assignment and _not_ about mutation, and the situation with the
compound assignment operators (which get overloaded with opOpAssign) needs
to be made very clear for both user-defined types and primitive types,
whereas the issue really isn't discussed at all with what's currently in the
DIP.

- Jonathan M Davis






More information about the dip.development mailing list