First Draft: Static Single Assignment
Quirin Schroll
qs.il.paperinik at gmail.com
Fri Jan 16 14:32:58 UTC 2026
On Wednesday, 3 December 2025 at 08:31:42 UTC, Walter Bright
wrote:
> On 11/27/2025 11:41 AM, Jonathan M Davis wrote:
>> Okay, what's the real motivation here?
>
> The motivation is to find a way to express "single assignment".
>
> Single assignment has gained traction in programming circles,
> as for long functions it can be hard to track if the value gets
> reassigned in the function or not. By tagging with `final` the
> compiler can enforce it to you.
That might be a fad. Something isn’t good because it’s popular.
The value of a thing must be established independent of its
popularity.
> I've been refactoring my code to use single assignment, as it
> makes the code more readable.
That’s a style. A linter is the tool to enforce a style. A linter
can be configured to ban things that a compiler shouldn’t ever
ban.
In this case, a team might decide: When a function is more than
25 lines of code long (counting all lines), then enforce single
assignment, except for variables that are lint-annotated to be
mutable. There can be project-specific exceptions that make sense.
It’s a much more flexible approach and does not burden the
compiler and requires no thorough type system analysis. It
doesn’t introduce core-language corner cases. Everyone knows
linters are best-effort and won’t catch all problems.
>> 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.
>
> `const` is different, as it is transitive.
Yes, and everyone knows `const` bans modification, not just
assignment.
>> Of course, that also leaves the question of "op assignment"
>
> They're disallowed for `final` declarations.
>
>> ```d
>> final int i = 42;
>> i += 10;
>> ```
>> should be legal,
>
> I'm sorry, but the point is to make that illegal.
What about a class type?
For classes, there’s assignment of the handle and `opAssign`.
They look identical in code, but they’re semantically very
different. You could argue that class `opAssign` isn’t
modification in the sense of `final` because it’s akin to
assigning the pointed-to `int` of an `int*`.
>> 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.
>
> Once a user defined type is constructed, if is final, then it
> cannot be modified.
So, it can only call `const` member functions? At that point, it
can be just `const`.
>> 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,
>> ```d
>> final int i = 42;
>> auto ptr = &i;
>> ```
>> is going to have to make `ptr` `const(int)*` or `const(int*)`
>
> or simply disallowed. I hadn't thought of that case, though
> there are surely more cases I didn't think of!
At least at this point, you should stop and re-assess.
>> 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
>> ```d
>> 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.
>
> I don't want to open that either, hence making it a storage
> class. I understand the desire to make it part of the type
> system, but I want to make it not part of the type system.
The problem is that you want a simple solution for something
complicated and tradeoffy.
>> 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.
>
> I intend `final` to be optional and have it not have a
> complicated set of rules for it. It is intended to be
> lightweight.
Again, it will have weird semantics because you want a simple
solution for something that’s actually complicated.
AFAICT, the yea-sayer see:
* A `final int` isn’t complicated, it’s just `const int`.
* A `final` reference type isn’t complicated, you can’t modify
the reference.
* A `final T[]` isn’t too complicated, just don’t allow appending
and reassignment.
Those can be handled with a template. I don’t know what’s up with
`std.experimental.Final`, but it can definitely work for those. I
tried implementing it in 2017 and realized: You need
specializations for all sorts of types.
The nay-sayers see:
* All the above are useless.
* A `final` struct type with indirections is useless or
complicated.
What follows is: If the solution mustn’t be complicated, it will
be useless.
I tried fixing `std.experimental.Final` and realized for
head-const, you absolutely have to make it a qualifier and a D
template can’t substitute for that.
The idea is easy enough: `final` makes the first layer of
indirection unmodifiable through this reference. That means you
can’t let a `final` struct call mutable member function, but if
the struct has indirections, `const` functions ask too much. You
need dedicated `final` functions that only protect the first
layer of the struct and allow for modification after that.
This is the bare minimum for `final` to work with structs and
it’s the only place where it needs to be a core-language feature.
>> 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.
>
> Compound assignment operators would not be allowed on final
> declarations. And it must be about preventing mutation, or it
> is worthless.
Even then it’s very close to being worthless. I’ve given up on
fixing `std.experimental.Final` back then for exactly that
reason. It’s not worth it. It can’t work for structs, the only
place something like it actually provides value.
The simple answer from the nay-sayers is: If you want to code in
single assignment style, just do not re-assign. Just don’t do it.
For most types, you can even use `const` to tie your hands. And
if you really can’t trust yourself or your team members with
something that simple, use a linter. After all, it’s
syntactically very simple.
And I totally get why you like making it a storage class and not
a qualifier: It makes the implementation easier, a lot easier. D
already has 9 qualifications if you count all distinct
combinations of qualifiers, and with `final` those become 13;
you’d have to mangle it; it affects overloading (but it requires
no new rules, since all relevant rules already exist for
`const`); it necessitates a member function attribute to apply it
to the implicit `this` parameter; probably more.
There’s another reason it’s not a great qualifier: It’s
head-`const`. What about head-`immutable`? Head-`shared`?
Head-`inout`?
I’m with Peter C on one aspect: `final` isn’t a good name.
More information about the dip.development
mailing list