First Draft: Static Single Assignment
Jonathan M Davis
newsgroup.d at jmdavisprog.com
Thu Dec 4 01:10:41 UTC 2025
On Wednesday, December 3, 2025 1:31:42 AM Mountain Standard Time Walter Bright via dip.development 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.
If what you want is truly single assignment, then that's not a question of
mutation. It's a question of assignment. For primitive types, there may
arguably be no difference, but there's an enormous difference for
user-defined types. And with how member functions work, preventing mutation
basically means that if a variable which is a user-defined type is final,
then it can only call const or inout member functions, which basically makes
it no different from const for user-defined types.
If what you want is truly head-const rather than preventing assignment, then
final cannot work as a storage class, because the member functions are going
to need head-const / final variants in order for final to actually be
head-const and not full-on const, because without that, the compiler won't
have any way to enforce the head-const behavior on a member function without
simply enforcing full on transitive const.
The problem should be easy to fix with classes (assuming that you don't
treat final scope class references any differently from normal final class
references), because the class reference could be treated as the pointer
that it really is (even if the type system can't normally distinguish
between a class and a reference), because you could just prevent assignment
while allowing the class itself to be treated as mutable, but that sort of
approach will not work with structs, because the data lives on the stack and
therefore is part of the "head" that's supposed to be const. And whether it
would make sense to allow it for scope class references is debatable, since
they live on the stack but are still references.
Honestly, this is looking to me like a feature that could work reasonably
well with primitive types but which is likely to be an utter disaster with
user-defined types as long as final is a storage class and not a type
qualifier. In order to work properly with user-defined types, it needs the
complexity that you're trying to avoid.
I also strongly question that having the compiler enforce any form of single
assignment is worth the trouble, particularly since I'm unaware of any
actual benefits that it might bring to code generation. The compiler should
already be able to determine whether a variable might have been mutated
without such a helper function, meaning that the benefit is purely to try to
make it easier for the programmer to catch it if they want to enforce single
assignment but don't want full-on const. And honestly, if a function is long
enough that the programmer can't trivially figure that out themself, then
the function is probably too complex.
> Once a user defined type is constructed, if is final, then it cannot be modified.
Then that basically means that final and const are going to be equivalent
for user-defined types, because without treating final as full-on const, the
compiler won't be able to prevent member functions from mutating the head of
the object.
> 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.
Honestly, I don't see how it can work properly if it's not part of the type
system - at least not with structs - because without it being part of the
type system, either you can only call const member functions on a final
object (meaning that final struct types are actually const), or final won't
actually prevent mutation. It could still prevent assignment specifically,
but if you want to prevent mutation with the current type system, that means
const.
So, as things stand, this feels like a total hack to me. I agree that making
final a type qualifier complicates things considerably, but if you don't do
it, then I don't see how final can possibly work properly with structs.
I also cringe to think how it would work in generic code if classes are
treated as head-const thanks to the fact that the have a reference, but
structs are treated as full-on const, because they live on the stack. If
classes are treated as fully const, then that problem probably goes away,
but it also means that final is basically useless for anything other than
primitive types.
- Jonathan M Davis
More information about the dip.development
mailing list