First Draft: Static Single Assignment

Quirin Schroll qs.il.paperinik at gmail.com
Tue Nov 18 13:31:00 UTC 2025


On Sunday, 16 November 2025 at 01:50:16 UTC, Walter Bright wrote:
> On 11/14/2025 11:13 PM, Walter Bright wrote:
>> https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.html
>
> Ooops! forgot the linky:
>
> https://github.com/WalterBright/documents/blob/master/final.md

It were much more useful as a qualifier. If it were a qualifier, 
it could be used in (almost?) all places it can be used as a 
storage class, but it could *also* be used for C/C++ interop, 
where D currently lacks the ability to represent `int* const`. A 
`final(int*)` would be the perfect fit:
```cpp
// C++ header
void f(int* const&);
void g(int* const*&);
```
```d
// D binding
extern(C++) void f(ref final int*); // final works as a qualifier 
or storage class
extern(C++) void g(ref final(int*)*); // final only works as a 
qualifier
```
If `g` mutates the `int`, there’s no way to correctly express it 
in D.

As a qualifier, it should actually mean head-const, not “can’t 
reassign.” For a type without indirections, `final` is equivalent 
to `const`, but for a type with indirections, e.g. `int*` or 
`int[]`, the three variants mutable, `const`, and `final` are 
drastically different and `final` objects require their own kind 
of member functions.

For classes, `final` is a great addition: A `final(Object)` can’t 
be rebound, but its mutable methods can be used. Currently, a 
`const(Object)` can’t be rebound and can only use `const` 
methods, but in a future Edition, we could make it so that a 
`const(Object)` actually *can* be rebound, and only a 
`final(const Object)` can’t. That would mean a `const(Object[])` 
is in fact short for `const(final const Object)[])`, so that 
indexing returns a `final(const Object)` by reference. You can’t 
reassign it (because `const` on the slice is transitive), but a 
copying the object handle drops the `final`.
```d
void f(const Object[] objs)
{
     static assert(is(typeof(obj[0]) == final const Object));
     ref const Object obj0 = obj[0]; // Error, `final` dropped
     const Object obj1 = obj[1]; // Okay, copy of the object handle
     obj1 = obj[0]; // Okay, `const` refers to the object, not the 
handle
}
```

For your mind model, an object handle is a pointer to “the 
underlying class object,” which I represent with `!` for this 
paragraph. That means, `Object` is `Object!*`, `final(Object)` 
means `final(Object!*)`, currently `const(Object)` means 
``const(Object!*)``, but after the change it would mean 
`const(Object!)*` and `final const Object` would be 
`final(const(Object!)*)`.

On non-class types, `const` subsumes `final`.

A `final T*` is basically a reference.
A `final T[]` can be read, but not e.g. appended, the elements 
are unaffected.
A `final T[K]` can be read from, but no new key–value pairs added 
or removed, however, the `T` values are unaffected.
A `final R delegate(T)` can’t be re-assigned, but it’s not broken 
when it mutates its context. (Note that a `const(R delegate(T))` 
should not be callable as the context might be mutated, but is 
reached through `const`; it’s a bug that it can be called.)

---

That would be a language change that warrants a DIP and might 
pull its weight.


More information about the dip.development mailing list