digitalmars.dip.development - First Draft: Static Single Assignment
- Walter Bright (1/1) Nov 14 https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assi...
- Richard (Rikki) Andrew Cattermole (3/5) Nov 15 Where is the draft? This is only a link to an ideas thread. No new
- Walter Bright (1/1) Nov 15 Oops! https://github.com/WalterBright/documents/blob/master/final.md
- Peter C (9/11) Nov 17 Is there a case for allowing the responsibility for upholding the
- Walter Bright (1/1) Nov 19 I'd say "no". Just delete the final keyword.
- Peter C (19/21) Nov 22 In C#, in order to guarantee thread safety and ensure safe object
- Walter Bright (4/4) Nov 24 Constructors in D are allowed to initialize fields that would be otherwi...
- Walter Bright (3/4) Nov 15 Ooops! forgot the linky:
- Nick Treleaven (15/16) Nov 16 ```d
- monkyyy (3/5) Nov 16 if its not `final int*` it would be repeating the ref type theory
- Walter Bright (3/9) Nov 16 Fortunately, `final` is a storage class and not a type, so type theory d...
- monkyyy (17/27) Nov 16 Type theory is in laws of physics, length * time = mph; square
- Walter Bright (6/6) Nov 19 What you're proposing is "head const", which is what C and C++ have. D h...
- Dukc (10/16) Nov 24 What issues does it cause that this DIP avoids? Even this DIP
- Walter Bright (2/10) Nov 24 It won't allow a pointer to the field.
- Dukc (6/17) Nov 24 I don't understand. The compiler would not allow a pointer to
- Walter Bright (2/7) Dec 02 final is not part of the type system, const is.
- Walter Bright (2/21) Nov 16 Great questions! I will think about it.
- Walter Bright (5/22) Nov 16 ```d
- Peter C (11/36) Nov 16 The auto keyword should properly deduce the full declaration of
- Peter C (9/12) Nov 17 No. I'm wrong, and I better correct myself.
- Walter Bright (1/1) Nov 23 Correctamundo
- Nick Treleaven (7/28) Nov 17 I think it needs to be `const(int)*` to protect `f` from ever
- Peter C (12/17) Nov 17 Again, I have to correct my thinking, and agree with you here.
- jmh530 (9/25) Nov 17 Would it make sense to make final/head-const part of the type
- Peter C (15/23) Nov 17 Wouldn't this change the re-engineering scope of final from
- Peter C (17/19) Nov 16 'final' (or 'init' as I prefer to call it) is a single-assignment
- Peter C (20/25) Nov 16 int i;
- Quirin Schroll (66/70) Nov 18 It were much more useful as a qualifier. If it were a qualifier,
- jmh530 (15/18) Nov 18 I can't say I was following all of what you were saying with
- Tim (14/19) Nov 18 For full C++ interop it would also be needed for the hidden
- jmh530 (2/18) Nov 18 Good point.
- jmh530 (9/29) Nov 18 Similarly, assuming this DIP is approved as-is, a newcomer to the
- Peter C (21/31) Nov 18 Ummm... it's a final method.
- jmh530 (4/25) Nov 19 My point was that if this DIP is approved, then a newbie to the
- Nick Treleaven (3/12) Nov 19 That's the same issue as `const int f() {}`, although at least
- Nick Treleaven (8/21) Nov 18 I think the `this` reference is already essentially `final` in a
- Tim (3/9) Nov 19 Yes, this is only relevant for structs.
- Walter Bright (2/2) Nov 23 What we'd like to do is make the 'this' parameter explicit on member fun...
- Richard (Rikki) Andrew Cattermole (16/18) Nov 23 We'll need to be very careful with this line of thinking:
- Walter Bright (3/7) Dec 03 We already disambiguate it for other attributes, which is why I think ab...
- Peter C (60/61) Nov 18 Consider this to be my complete rejection of further overloading
- Walter Bright (10/10) Nov 23 Ironically, C++ head const is routinely used "as if" it was transitive c...
- Paul Backus (23/30) Nov 27 Now, consider their effect on the following code:
- Meta (5/38) Nov 27 Ya, it introduces all sorts of complications. Hence my suggestion
- Walter Bright (1/1) Dec 02 Yup. If it is not enforced, it is as worthless as C++ 'const' is.
- Walter Bright (9/9) Dec 02 You are correct in the behavior.
- Paul Backus (13/24) Dec 02 I agree that making final a full-fledged type qualifier would add
- Jonathan M Davis (19/23) Dec 03 I agree with this 100%. What's proposed here seems like a mess of specia...
- Richard (Rikki) Andrew Cattermole (4/4) Nov 15 Three things I'd like to see here:
- monkyyy (13/17) Nov 15 ```d
- Peter C (22/43) Nov 15 This use of 'final' .. me no like!
- Kapendev (4/6) Nov 16 I don't think this is a good idea because `init` is a common
- Peter C (9/16) Nov 16 We've had the 'init' keyword in C# for many years now.
- Jonathan M Davis (14/31) Nov 18 1. init is already used in D by the language itself to give the value th...
- Peter C (11/51) Nov 18 It's highly unlikely that 'init' or 'fixed' (which is now my new
- Jonathan M Davis (5/11) Nov 19 D does not have contextual keywords, and Walter has rejected the idea wh...
- Peter C (3/16) Nov 19 yeepers! well.. good luck with it then ;-)
- Walter Bright (1/1) Nov 21 `final` is better anyway.
- jmh530 (3/4) Nov 21 ...is it possible to edit your configs so that people on the web
- Walter Bright (2/4) Dec 03 If you use the threaded view it shouldn't be a problem?
- Peter C (15/16) Nov 21 not to me it isn't:
- Peter C (20/21) Nov 29 // good luck interpreting this code...(or explaining it to
- Meta (4/17) Nov 19 That's not entirely true. `body` became a contextual keyword
- Walter Bright (5/8) Nov 19 Wweeeellll, there is some flexibility there:
- Peter C (47/56) Nov 29 Again, a *contextual* keyword like 'fixed' would solve the issue
- Walter Bright (4/9) Nov 19 Problematic, because one can modify it with some pointer manipulation
- Richard (Rikki) Andrew Cattermole (4/9) Nov 20 You can do this with stack variables also in @system code.
- Peter C (22/26) Nov 21 If you step outside the safety fence, then the problem is you,
- Peter C (25/35) Nov 21 and forloops?
- Peter C (33/33) Nov 21 On Friday, 21 November 2025 at 09:48:57 UTC, Peter C wrote:
- Richard (Rikki) Andrew Cattermole (3/48) Nov 21 Variables defined in a loop get extracted outside of it.
- Peter C (29/34) Nov 21 But aren't you refering to a performance optimization?
- user1234 (24/25) Nov 17 two notes:
- Nick Treleaven (8/30) Nov 17 The DIP example shows that non-mutable ref to a final variable is
- user1234 (4/38) Nov 17 Alright. Maybe a clarification like "a final parameter can never
- Walter Bright (1/1) Nov 19 I don't think these will be a problem. Your examples all should pass.
- Walter Bright (8/15) Nov 23 This should be an error. It is equivalent to:
- Lars Johansson (30/31) Nov 21 Hi all,
- Peter C (6/7) Nov 21 If you're posting in this thread, it would benefit all, if you
- Jonathan M Davis (65/66) Nov 27 Okay, what's the real motivation here? If it's to prevent assignment, th...
- Peter C (24/26) Nov 27 The 'real' motivation seems clear to me: It's a proposal for a
- Kapendev (5/13) Nov 27 The minimalist and pragmatic thing would be to not make it part
- Peter C (6/20) Nov 27 When 'final' (or 'fixed' as I prefer it) appears, it documents
- Kapendev (17/38) Nov 27 Example:
- Richard (Rikki) Andrew Cattermole (8/11) Nov 27 Most of the time we have to describe the difference between headconst
- Walter Bright (2/4) Dec 03 Immutable is a great innovation in D.
- Walter Bright (3/5) Dec 03 Immutable is solid code when dealing with multiple threads, as no
- Walter Bright (3/6) Dec 03 You can utterly ignore `final` and your code will work just fine. That's...
- Walter Bright (3/6) Dec 03 I agree with everything you wrote except that(!) I don't see a reason to...
- Peter C (16/23) Dec 03 Actually I now disagree with my assertion as well ;-)
- Peter C (32/40) Nov 27 Here is a 'simple' example of its value:
- Meta (4/46) Nov 27 The compiler can already guarantee that, because the reference to
- Walter Bright (20/61) Dec 03 The motivation is to find a way to express "single assignment".
- Richard (Rikki) Andrew Cattermole (25/50) Dec 03 That'll already be the case for @safe functions, but it shouldn't be
- Jonathan M Davis (59/69) Dec 03 If what you want is truly single assignment, then that's not a question ...
- Walter Bright (2/2) Dec 01 Initial implementation:
- Nick Treleaven (14/18) Dec 02 It would be more useful if `final ref` meant the pointed-to data
- Peter C (4/6) Dec 02 Nice stuff. Thankyou for your effort here.
- Peter C (19/21) Dec 02 With the changes, I was expecting an error here (which I didn't
- Walter Bright (4/4) Dec 03 `final` for fields is not currently implemented with the PR. I have susp...
- Richard (Rikki) Andrew Cattermole (3/9) Dec 03 Union's are @system, I'm sure there will be something that can be done
- Nick Treleaven (5/17) Dec 03 Just to note that final on a dynamic array `a` should mean that
- Peter C (11/29) Dec 03 Yes, I forgot how dynamic arrays work.
- Dom Disc (3/10) Dec 03 This doesn't seem to be very useful. If you want a dynamic array
- Peter C (20/23) Dec 03 No, I wanted an array that *can* change length (i.e. a dynamic
- Peter C (20/32) Dec 03 Essentially I was trying to do this in D (below is C# though)
- Jonathan M Davis (19/21) Dec 03 Well, the benefit would be basically the same as with a const dynamic ar...
On 15/11/2025 8:13 PM, Walter Bright wrote:https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/ Single_Assignment_1765.htmlWhere is the draft? This is only a link to an ideas thread. No new information or a write up.
Nov 15
On Sunday, 16 November 2025 at 01:50:32 UTC, Walter Bright wrote:Oops! https://github.com/WalterBright/documents/blob/master/final.mdIs there a case for allowing the responsibility for upholding the SSA guarantee to move from the compiler to the programmer? i.e. by breaking the SSA rule via an explicit cast. final int i = 3; void baz(int* p) {} baz(cast(int*) &i); // should this be allowed? Since D is also a systems programming languge, my intuition says yes, there is a case.
Nov 17
I'd say "no". Just delete the final keyword.
Nov 19
On Sunday, 16 November 2025 at 01:50:32 UTC, Walter Bright wrote:Oops! https://github.com/WalterBright/documents/blob/master/final.mdpublication, a single assignment [which thankfully uses the 'readonly' keyword instead of 'sealed'] requires that a member field (in a class or struct) can only be assigned a value once, either at the point of declaration or before the constructor finishes executing. It doesn't matter how that value is computed or where the assignment line is located, as long as it's within the constructor's execution path. Presumably, this same rule would apply in D? i.e. public class SomeClass { public readonly string someValue; void initValue() { someValue = "some value"; } // Error (SAA): A 'final' field cannot be assigned to (except in a constructor or a variable initializer) }
Nov 22
Constructors in D are allowed to initialize fields that would be otherwise disallowed. ensuring it is not particularly relevant to D.
Nov 24
On 11/14/2025 11:13 PM, Walter Bright wrote:https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.htmlOoops! forgot the linky: https://github.com/WalterBright/documents/blob/master/final.md
Nov 15
On Sunday, 16 November 2025 at 01:50:16 UTC, Walter Bright wrote:https://github.com/WalterBright/documents/blob/master/final.md```d final int f = 3; f = 4; // error, f is final int* p = &f; // error, cannot make mutable reference to final variable const int* pc = &f; // ok ``` So I assume `typeof(&f)` would be `const int*`? ```d int i; final int* pi = &i; auto p = pi; ``` Is `p` final or const?
Nov 16
On Sunday, 16 November 2025 at 22:08:02 UTC, Nick Treleaven wrote:So I assume `typeof(&f)` would be `const int*`?if its not `final int*` it would be repeating the ref type theory failures and its supposedly in the same group as const
Nov 16
On 11/16/2025 4:18 PM, monkyyy wrote:On Sunday, 16 November 2025 at 22:08:02 UTC, Nick Treleaven wrote:Fortunately, `final` is a storage class and not a type, so type theory does not apply.So I assume `typeof(&f)` would be `const int*`?if its not `final int*` it would be repeating the ref type theory failures and its supposedly in the same group as const
Nov 16
On Monday, 17 November 2025 at 01:09:48 UTC, Walter Bright wrote:On 11/16/2025 4:18 PM, monkyyy wrote:Type theory is in laws of physics, length * time = mph; square cube laws etc. You dont get to avoid it. The math people uses big stupid words to sound smarter(while being dumber), while they are busy proving 1+1==2 in 300 pages, Im going to tell ya if I put a T in a T[] and then I grab an element out of the box, I expect a T. `alias S=T[]; assert(is(typeof(S.init[0])==T));` Ref breaks this so I get to convert all refs to pointers in all apis thats have a depth larger then 1; I don't actually care about a safetyism keyword (and if its like const it is one) but for metaprogramming you should have it just work. Otherwise phoboes v3 will be even slower. if its not a `(final int)*` you should double check the behavior of nested final types `final(final int*)*[]` for type theory concerns. So that its consistent and compiles with basic meta programming patterns.On Sunday, 16 November 2025 at 22:08:02 UTC, Nick Treleaven wrote:Fortunately, `final` is a storage class and not a type, so type theory does not apply.So I assume `typeof(&f)` would be `const int*`?if its not `final int*` it would be repeating the ref type theory failures and its supposedly in the same group as const
Nov 16
What you're proposing is "head const", which is what C and C++ have. D has "transitive const". Try to change the type system to support head const is extremely destructive, and likely to be terribly confusing. That's why "final" is a storage class, which sidesteps the issue. The C++ method of making references is part of the type system and is extremely awkward.
Nov 19
On Thursday, 20 November 2025 at 05:31:08 UTC, Walter Bright wrote:What you're proposing is "head const", which is what C and C++ have. D has "transitive const". Try to change the type system to support head const is extremely destructive, and likely to be terribly confusing. That's why "final" is a storage class, which sidesteps the issue.What issues does it cause that this DIP avoids? Even this DIP will let you do ```D struct HeadConst(T) { final T field; alias field this; } ```
Nov 24
On 11/24/2025 10:58 AM, Dukc wrote:
What issues does it cause that this DIP avoids? Even this DIP will let you do
```D
struct HeadConst(T)
{ final T field;
alias field this;
}
```
It won't allow a pointer to the field.
Nov 24
On Monday, 24 November 2025 at 20:33:24 UTC, Walter Bright wrote:On 11/24/2025 10:58 AM, Dukc wrote:I don't understand. The compiler would not allow a pointer to head const qualified variable either (except if the pointer is also typed as pointing to const), were it built-in to the type system. What's the difference?What issues does it cause that this DIP avoids? Even this DIP will let you do ```D struct HeadConst(T) { final T field; alias field this; } ```It won't allow a pointer to the field.
Nov 24
On 11/24/2025 12:42 PM, Dukc wrote:I don't understand. The compiler would not allow a pointer to head const qualified variable either (except if the pointer is also typed as pointing to const), were it built-in to the type system. What's the difference?final is not part of the type system, const is.
Dec 02
On 11/16/2025 2:08 PM, Nick Treleaven wrote:On Sunday, 16 November 2025 at 01:50:16 UTC, Walter Bright wrote:Great questions! I will think about it.https://github.com/WalterBright/documents/blob/master/final.md```d final int f = 3; f = 4; // error, f is final int* p = &f; // error, cannot make mutable reference to final variable const int* pc = &f; // ok ``` So I assume `typeof(&f)` would be `const int*`? ```d int i; final int* pi = &i; auto p = pi; ``` Is `p` final or const?
Nov 16
On 11/16/2025 2:08 PM, Nick Treleaven wrote:On Sunday, 16 November 2025 at 01:50:16 UTC, Walter Bright wrote:`final` is not part of the type system. So typeof(&f) would be `int*`.https://github.com/WalterBright/documents/blob/master/final.md```d final int f = 3; f = 4; // error, f is final int* p = &f; // error, cannot make mutable reference to final variable const int* pc = &f; // ok ``` So I assume `typeof(&f)` would be `const int*`?```d int i; final int* pi = &i; auto p = pi; ``` Is `p` final or const?```d int* p = pi; ```
Nov 16
On Monday, 17 November 2025 at 01:08:58 UTC, Walter Bright wrote:On 11/16/2025 2:08 PM, Nick Treleaven wrote:The auto keyword should properly deduce the full declaration of the initializer (pi), not just its type. If auto *only* copied the type (int*), the resulting variable p would be a simple, mutable pointer (int* p). This would allow the user to immediately reassign it. The semantics of 'final int* pi = &i;' needs to be fully retained by the auto keyword, otherwise the safety and guarantees provided by the original declaration are lost in the new variable. In summary, it is *the duty* of auto to also copy the binding restriction!On Sunday, 16 November 2025 at 01:50:16 UTC, Walter Bright wrote:`final` is not part of the type system. So typeof(&f) would be `int*`.https://github.com/WalterBright/documents/blob/master/final.md```d final int f = 3; f = 4; // error, f is final int* p = &f; // error, cannot make mutable reference to final variable const int* pc = &f; // ok ``` So I assume `typeof(&f)` would be `const int*`?```d int i; final int* pi = &i; auto p = pi; ``` Is `p` final or const?```d int* p = pi; ```
Nov 16
On Monday, 17 November 2025 at 06:18:27 UTC, Peter C wrote:.. In summary, it is *the duty* of auto to also copy the binding restriction!No. I'm wrong, and I better correct myself. int i; final int* pi = &i; auto p = pi; The binding restriction (i.e. the storage class specifier 'final') is purely a local property of the variable pi and should not propagate to the new, copied variable p so yes, as you say, the deduced type of p should be -> int*
Nov 17
On Monday, 17 November 2025 at 01:08:58 UTC, Walter Bright wrote:On 11/16/2025 2:08 PM, Nick Treleaven wrote:I think it needs to be `const(int)*` to protect `f` from ever being mutated. If `&f` is part of a complex expression (e.g. `(*[&f][0])++`) I think practically we can only enforce that using `const`.```d final int f = 3; f = 4; // error, f is final int* p = &f; // error, cannot make mutable reference to final variable const int* pc = &f; // ok ``` So I assume `typeof(&f)` would be `const int*`?`final` is not part of the type system. So typeof(&f) would be `int*`.OK so `p` is mutable because it's a copy of `pi`. Makes sense, thanks.```d int i; final int* pi = &i; auto p = pi; ``` Is `p` final or const?```d int* p = pi; ```
Nov 17
On Monday, 17 November 2025 at 09:58:22 UTC, Nick Treleaven wrote:.. I think it needs to be `const(int)*` to protect `f` from ever being mutated. If `&f` is part of a complex expression (e.g. `(*[&f][0])++`) I think practically we can only enforce that using `const`.Again, I have to correct my thinking, and agree with you here. To enforce the guarantee of 'final', typeof(&f) must result in a pointer type that prevents data write access to f. Any other type (like a simple int*) would allow write access, thereby invalidating the guarantee provided by final. .................. final int f = 3; int* p = &f; // Error const int* pc = &f; // ok typeof(&f); // const(int)* ................
Nov 17
On Monday, 17 November 2025 at 01:08:58 UTC, Walter Bright wrote:On 11/16/2025 2:08 PM, Nick Treleaven wrote:Would it make sense to make final/head-const part of the type system? Other than that it makes it more difficult to implement. I recall there were a lot of discussions in the past about converting something that is head const to tail const, or maybe the reverse. I think the issue was that it was built-in for D's arrays, but you can't easily do that if you have your own aggregate. There seemed to have been some desire to have that functionality in the language.On Sunday, 16 November 2025 at 01:50:16 UTC, Walter Bright wrote:`final` is not part of the type system. So typeof(&f) would be `int*`.https://github.com/WalterBright/documents/blob/master/final.md```d final int f = 3; f = 4; // error, f is final int* p = &f; // error, cannot make mutable reference to final variable const int* pc = &f; // ok ``` So I assume `typeof(&f)` would be `const int*`?
Nov 17
On Monday, 17 November 2025 at 15:05:17 UTC, jmh530 wrote:Would it make sense to make final/head-const part of the type system? Other than that it makes it more difficult to implement. I recall there were a lot of discussions in the past about converting something that is head const to tail const, or maybe the reverse. I think the issue was that it was built-in for D's arrays, but you can't easily do that if you have your own aggregate. There seemed to have been some desire to have that functionality in the language.Wouldn't this change the re-engineering scope of final from low-impact (a local compiler rule check - essentially a hack) to high-impact (a fundamental redesign of the type system). It seems improbable [to me at least], that Walter would seriously consider fundamentally redesigning the type system. The most pragmatic option here, is the Minimal Viable Change (MVC) approach - i.e one that satisfies the guarantee of SSA immutability without incurring massive technical debt. - Goal: Guarantee SSA immutability. - Method: Use a simple declaration constraint (metadata) on the variable identifier. - Safety Net: Use the existing, well-understood mechanism of synthesizing a const pointer when the address is taken, which is the necessary "hack" to uphold the original final promise.
Nov 17
On Sunday, 16 November 2025 at 22:08:02 UTC, Nick Treleaven wrote:... So I assume `typeof(&f)` would be `const int*`?'final' (or 'init' as I prefer to call it) is a single-assignment restriction, and certainly not a type qualifier. so.. with that specific point in mind... final int f = 3; f = 4; // Error: Cannot assign to f. It has been constrained to a single-assignment restriction. int* p = &f; // Error, cannot create a mutable reference to a single-assignment variable. const int* pc = &f; // ok - The type of f is just int. - typeof(&f) -> taking the address of an int just yields an int*. - int* p = &f; // To preserve the integrity of the immutable binding (f = 4), this must be rejected by the compiler. - const int* pc = &f; // ok, as const helps to support the the single-assignment restriction -> that f has been assigned to the value 3.
Nov 16
On Sunday, 16 November 2025 at 22:08:02 UTC, Nick Treleaven wrote:.. int i; final int* pi = &i; auto p = pi; Is `p` final or const?int i; - A simple integer variable i is declared. Its value is mutable (can be changed). final int* pi = &i; - pi is a single-assignment pointer binding - the address stored in pi cannot be reassigned to a different address. So pi is conceptually immutable, but not technically in the same way a const pointer is, since const is a type qualifier, and -> binding restriction != a type qualifier. The result is the same, in that there is a binding lock, but one is *not* done by the type system (final), and one *is* done by the type system (const). "technically immutable" is still reserved for things defined by *type* qualifiers like const or immutable. (note: the integer value at that address is still mutable!) auto p = pi; - Since final (or init) is a binding restriction and not a type qualifier, then the deduced variable p must be final. That is, auto copies the binding restriction (final) and the base type (int*). The resulting variable, must be: final int∗
Nov 16
On Sunday, 16 November 2025 at 01:50:16 UTC, Walter Bright wrote:On 11/14/2025 11:13 PM, Walter Bright wrote: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.https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.htmlOoops! forgot the linky: https://github.com/WalterBright/documents/blob/master/final.md
Nov 18
On Tuesday, 18 November 2025 at 13:31:00 UTC, Quirin Schroll wrote:[snip] That would be a language change that warrants a DIP and might pull its weight.I can't say I was following all of what you were saying with respect to classes, but I'm generally sympathetic to this. That being said, this would be a much bigger change than what Walter is suggesting. Among the language maintainers, there is a lot of pushback on new features that will significantly increase complexity. I tend to think of it as a complexity/usefulness trade-off. On this basis, the question is whether the additional usefulness beyond Walter's suggestion would justify the additional complexity it would bring. I don't have a good sense of that, but providing an improved C++ interop story would be a positive. Regardless, this version might have a more successful adoption if it is released through the editions process.
Nov 18
On Tuesday, 18 November 2025 at 13:31:00 UTC, Quirin Schroll wrote: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 fitFor full C++ interop it would also be needed for the hidden `this` parameter of methods, but `final` on methods already means something different: ``` extern(C++) class C { void f() final; // Does final mean head const here? } ``` The syntax could distinguish between `final` before and after the declaration, but that could be confusing. It would be better to use a different keyword for head const with C++ compatibility.
Nov 18
On Tuesday, 18 November 2025 at 17:05:56 UTC, Tim wrote:On Tuesday, 18 November 2025 at 13:31:00 UTC, Quirin Schroll wrote:Good point.[...]For full C++ interop it would also be needed for the hidden `this` parameter of methods, but `final` on methods already means something different: ``` extern(C++) class C { void f() final; // Does final mean head const here? } ``` The syntax could distinguish between `final` before and after the declaration, but that could be confusing. It would be better to use a different keyword for head const with C++ compatibility.
Nov 18
On Tuesday, 18 November 2025 at 17:55:19 UTC, jmh530 wrote:On Tuesday, 18 November 2025 at 17:05:56 UTC, Tim wrote:Similarly, assuming this DIP is approved as-is, a newcomer to the language might be a little confused by the snippet below. ``` class C { final int f() {} } ``` Is the function returning a `final int` or is it a `final` method?On Tuesday, 18 November 2025 at 13:31:00 UTC, Quirin Schroll wrote:Good point.[...]For full C++ interop it would also be needed for the hidden `this` parameter of methods, but `final` on methods already means something different: ``` extern(C++) class C { void f() final; // Does final mean head const here? } ``` The syntax could distinguish between `final` before and after the declaration, but that could be confusing. It would be better to use a different keyword for head const with C++ compatibility.
Nov 18
On Tuesday, 18 November 2025 at 18:05:47 UTC, jmh530 wrote:
..
Similarly, assuming this DIP is approved as-is, a newcomer to
the language might be a little confused by the snippet below.
```
class C {
final int f() {}
}
```
Is the function returning a `final int` or is it a `final`
method?
Ummm... it's a final method.
module mymodule;
safe:
private:
import std;
class C
{
final int f()
{
return 0;
}
}
void main()
{
if (__traits(isFinalFunction, C.f))
writeln("C.f is a final method");
else
writeln("C.f is not a final method");
}
// C.f is a final method
Nov 18
On Wednesday, 19 November 2025 at 04:33:38 UTC, Peter C wrote:
[snip]
Ummm... it's a final method.
module mymodule;
safe:
private:
import std;
class C
{
final int f()
{
return 0;
}
}
void main()
{
if (__traits(isFinalFunction, C.f))
writeln("C.f is a final method");
else
writeln("C.f is not a final method");
}
// C.f is a final method
My point was that if this DIP is approved, then a newbie to the
programming language might get confused as to whether `f` is a
`final` method or returns a `final int`.
Nov 19
On Tuesday, 18 November 2025 at 18:05:47 UTC, jmh530 wrote:
Similarly, assuming this DIP is approved as-is, a newcomer to
the language might be a little confused by the snippet below.
```
class C {
final int f() {}
}
```
Is the function returning a `final int` or is it a `final`
method?
That's the same issue as `const int f() {}`, although at least
final isn't proposed as a type constructor.
Nov 19
On Tuesday, 18 November 2025 at 17:05:56 UTC, Tim wrote:For full C++ interop it would also be needed for the hidden `this` parameter of methods,I think the `this` reference is already essentially `final` in a D class. Do you mean for a struct so its fields are head const?but `final` on methods already means something different: ``` extern(C++) class C { void f() final; // Does final mean head const here? } ``` The syntax could distinguish between `final` before and after the declaration, but that could be confusing. It would be better to use a different keyword for head const with C++ compatibility.Walter supports* allowing explicit `this` parameters, which could solve the ambiguity if the `final` attribute on a method always means 'not overridable'. So to mark the `this` parameter as `final` you would have to declare it as the first parameter. *https://forum.dlang.org/post/10004mc$23m1$1 digitalmars.com
Nov 18
On Tuesday, 18 November 2025 at 22:09:56 UTC, Nick Treleaven wrote:On Tuesday, 18 November 2025 at 17:05:56 UTC, Tim wrote:Yes, this is only relevant for structs.For full C++ interop it would also be needed for the hidden `this` parameter of methods,I think the `this` reference is already essentially `final` in a D class. Do you mean for a struct so its fields are head const?
Nov 19
What we'd like to do is make the 'this' parameter explicit on member functions, which would clear up a lot of confusing cases.
Nov 23
On 23/11/2025 10:46 PM, Walter Bright wrote:What we'd like to do is make the 'this' parameter explicit on member functions, which would clear up a lot of confusing cases.We'll need to be very careful with this line of thinking: ```d scope { void func() { } } scope: void func2() { } ``` Doing stuff like this is good, and useful. I use it, a lot. Explicitly stating that final will bind to the function declaration, not its this pointer is required. Really the only way to get out of this is to use different keywords.
Nov 23
On 11/23/2025 6:44 AM, Richard (Rikki) Andrew Cattermole wrote:Explicitly stating that final will bind to the function declaration, not its this pointer is required. Really the only way to get out of this is to use different keywords.We already disambiguate it for other attributes, which is why I think about making the `this` explicit.
Dec 03
On Tuesday, 18 November 2025 at 13:31:00 UTC, Quirin Schroll wrote:...Consider this to be my complete rejection of further overloading the meaning of final in D. The proposal to further overload 'final', to serve as the solutiuon for Single Assignment Annotation (SAA), and/or Foreign Function Interface (FFI) compatibility, would create an unnecessary semantic collision within the D programming language. What is the semantic collision I'm concerned about? D already uses final for structural constraints within the Object-Oriented paradigm, and in an orthogonal use, for control flow constraint on the switch statement. Structural constraints within the Object-Oriented paradigm: - A class can be declared 'final' to prevent subclassing - A class method can be declared 'final' to prevent a derived class overriding it - Interfaces can define 'final' methods Control Flow Constraint (Orthogonal Use) - A final switch (Exhaustiveness Check). These two uses - inheritance policy and control flow exhaustiveness - have no logical or semantic connection, they are orthogonal to each other. So currently, D developers must already mentally juggle orthogonal concepts under a single keyword. Introducing a third, unrelated meaning for final, in relation to FFI/SAA, would cause a more severe semantic collision. 'final' should not be overloaded any further! That is final! Recommended Alternative: The optimal solution is to introduce a new contextual keyword -> 'fixed' (which currently does not exist in D as an identifier, and so eliminates any semantic collision risk). The compiler would treat 'fixed' as a keyword only in a specific type qualification context, meaning existing code that uses 'fixed' as an identifier outside of that context would not break. - using 'fixed' for SSA - The concept of SAA is about declaring a variable whose binding is fixed after initialization. The keyword 'fixed' directly conveys this intent. fixed(void delegate()) fixedIncrements = { counter++; }; // the variable fixedIncrements is an SAA - using 'fixed' for FFI - The concept of FFI (Foreign Function Interface) compatibility is about declaring a pointer's address as non-reassignable to correctly model the C/C++ type T* const. The keyword 'fixed' directly conveys this intent. fixed(int)* // models the C++ type int* const languages that use 'fixed'. have no semantic confusion as to what 'fixed' would mean in D). **If we settle on a new term, that would really help when providing your example code, and we can all see how well the new term would fit, or not fit. At the moment, in my mind, final does not fit. I had a similar problem with the term 'private(this)' in OpenD. I then changed it to 'scopeprivate' in my fork, and all of a sudden I felt completely comfortable using it. I also have a problem using 'final' for anything other than what it currently means in D.
Nov 18
Ironically, C++ head const is routinely used "as if" it was transitive const (as C++ has no way to specify transitive const). In my experience with C++ (and C), types that are legitimately "pointer to const pointer to pointer to int" are extremely rate. This is a giant hole in C++'s type system. Some C++ programmers make use of this to create "logical const" objects, where they pretend it is constant when it isn't. It is used for self-initializing objects. There are better ways to do this, though. And lying to the user is not a good strategy. It isn't a good idea for D to adopt this.
Nov 23
On Sunday, 16 November 2025 at 01:50:16 UTC, Walter Bright wrote:On 11/14/2025 11:13 PM, Walter Bright wrote:Take these two rules:https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.htmlOoops! forgot the linky: https://github.com/WalterBright/documents/blob/master/final.mdA `ref` cannot be taken of a `final` declaration`final` can be applied to function parameters, but overloading is not affected. Hence, it has no effect on name mangling.Now, consider their effect on the following code: void foo(ref int n) {} void foo(ref const int n) {} void bar(ref inout int n) {} void baz(T)(auto ref T t) {} void main() { final int n; foo(n); // Error - selects non-const overload bar(n); // Error - substitutes mutable for inout baz(n); // Error - calls baz!int(ref int) } In all of these cases, the function calls *could* be made to work. But because `final` is a storage class, not a type qualifier, it cannot participate in overload selection, `inout` substitution, or template instantiation, and the compiler cannot figure out how to call the function correctly. It is worth noting that all three calls would succeed if `final` were replaced with `const`. If `final` is implemented as proposed, these kinds of edge cases will severely hinder its adoption.
Nov 27
On Friday, 28 November 2025 at 03:25:27 UTC, Paul Backus wrote:On Sunday, 16 November 2025 at 01:50:16 UTC, Walter Bright wrote:Ya, it introduces all sorts of complications. Hence my suggestion to take the simpler route and allow final variables to be modified through references and pointers. But at that point it just becomes a lint and there's very little benefit.On 11/14/2025 11:13 PM, Walter Bright wrote:Take these two rules:https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.htmlOoops! forgot the linky: https://github.com/WalterBright/documents/blob/master/final.mdA `ref` cannot be taken of a `final` declaration`final` can be applied to function parameters, but overloading is not affected. Hence, it has no effect on name mangling.Now, consider their effect on the following code: void foo(ref int n) {} void foo(ref const int n) {} void bar(ref inout int n) {} void baz(T)(auto ref T t) {} void main() { final int n; foo(n); // Error - selects non-const overload bar(n); // Error - substitutes mutable for inout baz(n); // Error - calls baz!int(ref int) } In all of these cases, the function calls *could* be made to work. But because `final` is a storage class, not a type qualifier, it cannot participate in overload selection, `inout` substitution, or template instantiation, and the compiler cannot figure out how to call the function correctly. It is worth noting that all three calls would succeed if `final` were replaced with `const`. If `final` is implemented as proposed, these kinds of edge cases will severely hinder its adoption.
Nov 27
Yup. If it is not enforced, it is as worthless as C++ 'const' is.
Dec 02
You are correct in the behavior. Whether this will cause problems or not, is a little less clear. Inserting `final` into the overload process adds another axis and so a great deal of complexity. I initially designed a very simple overload system, but things have gotten out of hand. I don't wish to repeat the C++ error of nobody knowing how overloading works; programmers just try random things until they get something that works. We are already perilously close to that. Personally, overloading is overused. It also would change the name mangling, another disruption.
Dec 02
On Tuesday, 2 December 2025 at 08:03:29 UTC, Walter Bright wrote:You are correct in the behavior. Whether this will cause problems or not, is a little less clear. Inserting `final` into the overload process adds another axis and so a great deal of complexity. I initially designed a very simple overload system, but things have gotten out of hand. I don't wish to repeat the C++ error of nobody knowing how overloading works; programmers just try random things until they get something that works. We are already perilously close to that. Personally, overloading is overused. It also would change the name mangling, another disruption.I agree that making final a full-fledged type qualifier would add a considerable amount of additional complexity to the language. However, I do not think the best solution to that problem is to implement it in this strange, halfway-in-between state, where it's technically a storage class but sometimes *behaves* like a type qualifier. This is the exact kind of confusing dessert topping/floor wax duality that you (rightly) criticize in C++'s reference types. If we are not willing to commit to making final a fully-fledged type qualifier, then I think we would be better off not including it in the language at all. If it's not worth doing right, it's not worth doing.
Dec 02
On Tuesday, December 2, 2025 4:04:27 PM Mountain Standard Time Paul Backus via dip.development wrote:If we are not willing to commit to making final a fully-fledged type qualifier, then I think we would be better off not including it in the language at all. If it's not worth doing right, it's not worth doing.I agree with this 100%. What's proposed here seems like a mess of special rules (with more likely to be added to deal with more complex situations), and it's not going to interact well with user-defined types at all. And if it's just useful for primitive types, what's that mean? That it makes it possible to have a const pointer to mutable data or a mutable slice of const data? I don't understand why that's useful enough to add a feature like this, particularly if all it's really doing is making it so that a programmer can catch when they assign a value to such a variable within a function that's long enough that they can't catch it manually. IMHO, if the function is long enough that that's an issue, then it's probably too long and needs to be refactored. Honestly, the more I look at the current state of things, the more I'm inclined to think that storage classes in general are a mistake. And both this proposal and DIP 1000 are examples of how trying to avoid a proper type qualifier requires confusing special cases. I understand the desire to avoid complicating the type system with more type qualifiers, but storage classes largely seem to just end up being warts as a result. - Jonathan M Davis
Dec 03
Three things I'd like to see here: 1. Function parameters 2. Fields 3. Globals
Nov 15
On Sunday, 16 November 2025 at 02:43:03 UTC, Richard (Rikki) Andrew Cattermole wrote:Three things I'd like to see here: 1. Function parameters 2. Fields 3. Globals```d struct writeonce(T){ final T data; final bool hasBeenSet; auto opAssign(T data_){ assert( ! hasBeenSet); return data=data_; }} ``` --- `void foo(ref final int a)` it would need to be ref to do anything
Nov 15
On Sunday, 16 November 2025 at 03:12:08 UTC, monkyyy wrote:On Sunday, 16 November 2025 at 02:43:03 UTC, Richard (Rikki) Andrew Cattermole wrote:This use of 'final' .. me no like! Seeing 'final' makes me immediately think about inheritance and method overriding. That extra disambiguation required here, increases my cognitive load - which me also no like! Just the use of this overloaded term 'final' turns me off any proposal that wants to use it - yet again! An explicit keyword that communicates directly one thing, and one thing only - "assign once during construction", will make the code's meaning both immediate and unambiguous. Keywords should communicate intent directly. I propose intead, 'init' - it (at least to me) directly evokes an initialization context for the construction of a variable. This is not bikeshedding. It's not trivial nitpicking. Choosing the right primitive here needs to be a well thought out design decision. 'final' would almost certainly negatively affect cognitive load, and the programmers ability to quickly reason about code - not to mention 3rd party tools. Any proposal using that overloaded term 'final' will get the thumbs down from me.Three things I'd like to see here: 1. Function parameters 2. Fields 3. Globals```d struct writeonce(T){ final T data; final bool hasBeenSet; auto opAssign(T data_){ assert( ! hasBeenSet); return data=data_; }} ``` --- `void foo(ref final int a)` it would need to be ref to do anything
Nov 15
On Sunday, 16 November 2025 at 04:16:56 UTC, Peter C wrote:I propose intead, 'init' - it (at least to me) directly evokes an initialization context for the construction of a variable.I don't think this is a good idea because `init` is a common constant or function name. Not D specific also, just check some code in other languages.
Nov 16
On Sunday, 16 November 2025 at 09:59:19 UTC, Kapendev wrote:On Sunday, 16 November 2025 at 04:16:56 UTC, Peter C wrote:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/init 'sealed' would have done. 'sealed' is D's 'final' btw - and both of those invoke an OOP context (structural inheritance and type extension). I would have thought all the anti-oop people would have given me a +1 for suggesting 'init' instead of final ;-)I propose intead, 'init' - it (at least to me) directly evokes an initialization context for the construction of a variable.I don't think this is a good idea because `init` is a common constant or function name. Not D specific also, just check some code in other languages.
Nov 16
On Sunday, November 16, 2025 2:48:32 PM Mountain Standard Time Peter C via dip.development wrote:On Sunday, 16 November 2025 at 09:59:19 UTC, Kapendev wrote:1. init is already used in D by the language itself to give the value that a type is default-initialized with - e.g. T.init, int.init, etc. 2. Adding _any_ keyword is a breaking change, so the odds of it happening are pretty low in general. If there's a strong case for it with a particular feature, then it might happen, but if an existing keyword can reasonably be used instead (or some other syntax which wouldn't be a breaking change), that's almost certainly what's going to happen when adding a feature. It _might_ happen with a new edition, but even then, it's _highly_ unlikely that init would be an acceptable keyword given how it's already used. So, regardless of how much sense it would or wouldn't make to use init for something like this if we weren't worried about breaking existing code, it's clearly not going to happen. - Jonathan M DavisOn Sunday, 16 November 2025 at 04:16:56 UTC, Peter C wrote:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/init 'sealed' would have done. 'sealed' is D's 'final' btw - and both of those invoke an OOP context (structural inheritance and type extension). I would have thought all the anti-oop people would have given me a +1 for suggesting 'init' instead of final ;-)I propose intead, 'init' - it (at least to me) directly evokes an initialization context for the construction of a variable.I don't think this is a good idea because `init` is a common constant or function name. Not D specific also, just check some code in other languages.
Nov 18
On Tuesday, 18 November 2025 at 10:24:17 UTC, Jonathan M Davis wrote:On Sunday, November 16, 2025 2:48:32 PM Mountain Standard Time Peter C via dip.development wrote:It's highly unlikely that 'init' or 'fixed' (which is now my new preference) .. or whatever it ends up being called, is currently being used in existing code as an identifier preceding the initialization of a variable. It would be contextual keyword - the context being -> variable declaration and assignment. The chances of an existing piece of D code having the following structure are extremely low: private fixed int x = 10;On Sunday, 16 November 2025 at 09:59:19 UTC, Kapendev wrote:1. init is already used in D by the language itself to give the value that a type is default-initialized with - e.g. T.init, int.init, etc. 2. Adding _any_ keyword is a breaking change, so the odds of it happening are pretty low in general. If there's a strong case for it with a particular feature, then it might happen, but if an existing keyword can reasonably be used instead (or some other syntax which wouldn't be a breaking change), that's almost certainly what's going to happen when adding a feature. It _might_ happen with a new edition, but even then, it's _highly_ unlikely that init would be an acceptable keyword given how it's already used. So, regardless of how much sense it would or wouldn't make to use init for something like this if we weren't worried about breaking existing code, it's clearly not going to happen. - Jonathan M DavisOn Sunday, 16 November 2025 at 04:16:56 UTC, Peter C wrote:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/init init evokes the context of initialization, which is not what 'sealed' is D's 'final' btw - and both of those invoke an OOP context (structural inheritance and type extension). I would have thought all the anti-oop people would have given me a +1 for suggesting 'init' instead of final ;-)I propose intead, 'init' - it (at least to me) directly evokes an initialization context for the construction of a variable.I don't think this is a good idea because `init` is a common constant or function name. Not D specific also, just check some code in other languages.
Nov 18
On Tuesday, November 18, 2025 2:26:50 PM Mountain Standard Time Peter C via dip.development wrote:It's highly unlikely that 'init' or 'fixed' (which is now my new preference) .. or whatever it ends up being called, is currently being used in existing code as an identifier preceding the initialization of a variable. It would be contextual keyword - the context being -> variable declaration and assignment.D does not have contextual keywords, and Walter has rejected the idea when it has been proposed in the past. In D, if an identifier is a keyword, it's a keyword everywhere. - Jonathan M Davis
Nov 19
On Wednesday, 19 November 2025 at 08:29:17 UTC, Jonathan M Davis wrote:On Tuesday, November 18, 2025 2:26:50 PM Mountain Standard Time Peter C via dip.development wrote:yeepers! well.. good luck with it then ;-)It's highly unlikely that 'init' or 'fixed' (which is now my new preference) .. or whatever it ends up being called, is currently being used in existing code as an identifier preceding the initialization of a variable. It would be contextual keyword - the context being -> variable declaration and assignment.D does not have contextual keywords, and Walter has rejected the idea when it has been proposed in the past. In D, if an identifier is a keyword, it's a keyword everywhere. - Jonathan M Davis
Nov 19
On Friday, 21 November 2025 at 18:49:32 UTC, Walter Bright wrote:`final` is better anyway....is it possible to edit your configs so that people on the web interface can see specifically what comments you're reply to?
Nov 21
On 11/21/2025 11:32 AM, jmh530 wrote:...is it possible to edit your configs so that people on the web interface can see specifically what comments you're reply to?If you use the threaded view it shouldn't be a problem?
Dec 03
On Friday, 21 November 2025 at 18:49:32 UTC, Walter Bright wrote:`final` is better anyway.not to me it isn't: final class Base { final void printInfo() {} final Base finalFieldRef; } The use of 'final' for SAA will always invite a semantic collision here, requiring the programmer to constantly pause, recall the specific rules here with regards to the use of 'final' - based on its context, thus slowing down comprehension and introducing the risk of misinterpreting the codes contract - and readers include us code reviewers! What is needed here, is a dedicated, non-colliding (contexual) keyword - not a convenient compiler hack.
Nov 21
On Friday, 21 November 2025 at 18:49:32 UTC, Walter Bright wrote:`final` is better anyway.// good luck interpreting this code...(or explaining it to newcomers to D). final class Config { final: void baz() { } int port = 8080; string host = "localhost"; } // my alternative - where there is no ambiguity and no cognitive overhead! final class Config { final: void baz() { } fixed: int port = 8080; fixed string host = "localhost"; }
Nov 29
On Wednesday, 19 November 2025 at 08:29:17 UTC, Jonathan M Davis wrote:On Tuesday, November 18, 2025 2:26:50 PM Mountain Standard Time Peter C via dip.development wrote:That's not entirely true. `body` became a contextual keyword after DIP 1003 IIRC.It's highly unlikely that 'init' or 'fixed' (which is now my new preference) .. or whatever it ends up being called, is currently being used in existing code as an identifier preceding the initialization of a variable. It would be contextual keyword - the context being -> variable declaration and assignment.D does not have contextual keywords, and Walter has rejected the idea when it has been proposed in the past. In D, if an identifier is a keyword, it's a keyword everywhere. - Jonathan M Davis
Nov 19
On 11/19/2025 12:29 AM, Jonathan M Davis wrote:D does not have contextual keywords, and Walter has rejected the idea when it has been proposed in the past. In D, if an identifier is a keyword, it's a keyword everywhere.Wweeeellll, there is some flexibility there: `extern (C) int a;` `pragma(msg, "hello"); D has a successful pragmatic approach to this.
Nov 19
On Tuesday, 18 November 2025 at 10:24:17 UTC, Jonathan M Davis wrote:... 2. Adding _any_ keyword is a breaking change, so the odds of it happening are pretty low in general. If there's a strong case for it with a particular feature, then it might happen, but if an existing keyword can reasonably be used instead (or some other syntax which wouldn't be a breaking change), that's almost certainly what's going to happen when adding a feature. ... - Jonathan M DavisAgain, a *contextual* keyword like 'fixed' would solve the issue of compatability. Why do I think something other than 'final' is needed? Because of this 'real world' example: It's from: https://github.com/ProjectDVN/dvn/blob/main/source/dvn/application.d i.e. adding yet another overloaded meaning to 'final' would be conceptually confusing at the very least. I would call it out further, as being poor language design! public final class Application { private: FontCollection _fonts; bool _running; Tid _uiTid; int _fps; Color _defaultWindowColor; Window[] _windows; bool _allowWASDMovement; size_t _concurrencyLevel; size_t _messageLevel; bool _isDebugMode; public: final: this(int defaultFps = 60) { this(getColorByName("white"), defaultFps); } this(Color defaultWindowColor,int defaultFps = 60) { if (defaultFps <= 0 || defaultFps > 240) { throw new ApplicationException("Invalid default fps."); } _defaultWindowColor = defaultWindowColor; _fonts = new FontCollection; _fps = defaultFps; _windows = []; _concurrencyLevel = 4; _messageLevel = 42; if (!_app) { _app = this; } }
Nov 29
On 11/15/2025 6:43 PM, Richard (Rikki) Andrew Cattermole wrote:Three things I'd like to see here: 1. Function parametersYes2. FieldsProblematic, because one can modify it with some pointer manipulation3. GlobalsYes
Nov 19
On 20/11/2025 7:03 PM, Walter Bright wrote:On 11/15/2025 6:43 PM, Richard (Rikki) Andrew Cattermole wrote:You can do this with stack variables also in system code. But not in safe. Fields are not special.Three things I'd like to see here: 2. FieldsProblematic, because one can modify it with some pointer manipulation
Nov 20
On Thursday, 20 November 2025 at 06:03:03 UTC, Walter Bright wrote:... 2. Fields Problematic, because one can modify it with some pointer manipulationIf you step outside the safety fence, then the problem is you, not the safety fence. i.e. With pointer manipulation (pointer bypass), the SAA rule is not broken. Rather its enforcement mechanism is circumvented by using a feature (a pointer) that operates at a lower level than the SAA contract is designed to monitor. The SAA feature itself, still would remain highly valuable here, for preventing accidental errors in safe code. The fact that a dedicated, unsafe operation can bypass a safety check doesn't make the check problematic. Accidental errors are a far more common and insidious problem than deliberate, unsafe memory operations. High-level operation using the variable name (this.field = newValue;) // inside the fence - SAA successfully blocks the high-level reassignment with a compile-time error. Low-level operation using memory addresses (*ptr_to_field = newAddress;). // outside the fence - The unsafe code circumvents the compiler's SAA check entirely.
Nov 21
On Thursday, 20 November 2025 at 06:03:03 UTC, Walter Bright wrote:On 11/15/2025 6:43 PM, Richard (Rikki) Andrew Cattermole wrote:and forloops? ----- module mymodule; safe: private: import std; void main() { int[] arr = [100, 200, 300]; int extra = 999; // Here, 'fixed' ensures that the loop variable remains bound // to the correct array element throughout its iteration. // foreach (fixed ref element; arr) { // element is now bound as an alias to arr[i] element = 500; // OK: modifying the value being referenced -> arr[i] element = extra; // ERROR: reassigning a fixed reference binding is not allowed. } } -----Three things I'd like to see here: 1. Function parametersYes2. FieldsProblematic, because one can modify it with some pointer manipulation3. GlobalsYes
Nov 21
On Friday, 21 November 2025 at 09:48:57 UTC, Peter C wrote:and forloops? (a fuller coverage of examples in this post) ----- module mymodule; safe: private: import std; void main() { int[] arr = [100, 200, 300]; int extra = 999; foreach (fixed ref element; arr) { // element is now bound as an alias to arr[i] element = 500; // OK: modifying the value being referenced -> arr[i] element = extra; // ERROR (SAA): Cannot reassign a fixed reference binding. } for (fixed int i = 0; i < arr.length; i++) // SAA on index 'i' { i = 5; // ERROR (SAA): Cannot reassign the fixed index 'i' // ... } for (int i = 0; i < arr.length; i++) { fixed ref int currentElement = arr[i]; currentElement = 500; // ok -> modifies arr[i] currentElement = extra; // ERROR (SAA): Cannot reassign a fixed reference binding. } } -----
Nov 21
On 21/11/2025 10:48 PM, Peter C wrote:On Thursday, 20 November 2025 at 06:03:03 UTC, Walter Bright wrote:Variables defined in a loop get extracted outside of it. They shouldn't need a dedicated section, but good to bring it up!On 11/15/2025 6:43 PM, Richard (Rikki) Andrew Cattermole wrote:and forloops? ----- module mymodule; safe: private: import std; void main() { int[] arr = [100, 200, 300]; int extra = 999; // Here, 'fixed' ensures that the loop variable remains bound // to the correct array element throughout its iteration. // foreach (fixed ref element; arr) { // element is now bound as an alias to arr[i] element = 500; // OK: modifying the value being referenced -> arr[i] element = extra; // ERROR: reassigning a fixed reference binding is not allowed. } } -----Three things I'd like to see here: 1. Function parametersYes2. FieldsProblematic, because one can modify it with some pointer manipulation3. GlobalsYes
Nov 21
On Friday, 21 November 2025 at 15:04:27 UTC, Richard (Rikki) Andrew Cattermole wrote:On 21/11/2025 10:48 PM, Peter C wrote:But aren't you refering to a performance optimization? I'm referring to semantic safety. The purpose of using 'fixed' in the code below, is to prevent an illegal or erroneous reference reassignment inside the loop body. It's an independent concern, not at all related to optimization. ----- module mymodule; safe: private: import std; void main() { int[] arr = [100, 200, 300]; int extra = 999; // Here, 'fixed' ensures that the loop variable remains bound // to the correct array element throughout its iteration. // foreach (fixed ref element; arr) { // element is now bound as an alias to arr[i] element = 500; // OK: modifying the value being referenced -> arr[i] element = extra; // ERROR: reassigning a fixed reference binding is not allowed. } } -----[...]Variables defined in a loop get extracted outside of it. They shouldn't need a dedicated section, but good to bring it up!
Nov 21
On Saturday, 15 November 2025 at 07:13:13 UTC, Walter Bright wrote:https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.htmltwo notes: 1. The description requires to specify that pass-by-ref a final is (_always almost_) illegal. 2. what about parameter storage classes ? (since the StorageClass rule is shared by VarDecl and Param) ```d void v(final int p = 0); v(1); ``` VS ```d void v(final int p = 0); v(); ``` is the default argument stands for a single assignment ? which leads to ask what is the right behavior (given first note) for ```d void v(final ref int); final int arg; v(arg); ```
Nov 17
On Monday, 17 November 2025 at 10:25:47 UTC, user1234 wrote:1. The description requires to specify that pass-by-ref a final is (_always almost_) illegal.The DIP example shows that non-mutable ref to a final variable is OK, despite it saying:A ref cannot be taken of a final declaration:I think that sentence should be "A mutable ref cannot...".2. what about parameter storage classes ? (since the StorageClass rule is shared by VarDecl and Param) ```d void v(final int p = 0); v(1); ``` VS ```d void v(final int p = 0); v(); ```I think both calls should work. `p` can't be modified inside v's body.is the default argument stands for a single assignment ?The default argument is only used when there's no argument.which leads to ask what is the right behavior (given first note) for ```d void v(final ref int); final int arg; v(arg); ```I think that should work, because the call can't modify `arg`.
Nov 17
On Monday, 17 November 2025 at 11:28:43 UTC, Nick Treleaven wrote:On Monday, 17 November 2025 at 10:25:47 UTC, user1234 wrote:Alright. Maybe a clarification like "a final parameter can never be reassigned in the body" could be added. To make things very clear.1. The description requires to specify that pass-by-ref a final is (_always almost_) illegal.The DIP example shows that non-mutable ref to a final variable is OK, despite it saying:A ref cannot be taken of a final declaration:I think that sentence should be "A mutable ref cannot...".2. what about parameter storage classes ? (since the StorageClass rule is shared by VarDecl and Param) ```d void v(final int p = 0); v(1); ``` VS ```d void v(final int p = 0); v(); ```I think both calls should work. `p` can't be modified inside v's body.is the default argument stands for a single assignment ?The default argument is only used when there's no argument.which leads to ask what is the right behavior (given first note) for ```d void v(final ref int); final int arg; v(arg); ```I think that should work, because the call can't modify `arg`.
Nov 17
I don't think these will be a problem. Your examples all should pass.
Nov 19
On 11/17/2025 2:25 AM, user1234 wrote:which leads to ask what is the right behavior (given first note) for ```d void v(final ref int); final int arg; v(arg); ```This should be an error. It is equivalent to: ```d final int arg; ref int rarg = arg; // error ref const(int) rarg = arg; // ok ``` I.e. a ref to a final should not be able to modify the final.
Nov 23
On Saturday, 15 November 2025 at 07:13:13 UTC, Walter Bright wrote:https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.htmlHi all, I have been following this blog since the beginning with great interest. I really like the D language, and you who contribute, most of you seem to be smart people. I actually used D/Tango to build a small production app, but was disheartened by the fight between Tango/Phobos and stopped using D. Since then I started up D projects a few times, but due time constraints I always ended up using more hilvl alternatives, mostly PHP! If you go back to the starting post of this thread; Item 2, the answer is 'yes'. The nitpicking following I found interesting, but it is utterly unproductive; it does not go anywhere (as most of the discussions here). If I were you I would concentrate on: 1 simple connectivity to languages and systems e.g. phyton and SAP. 2 more capable 'auto', e.g. not many know or care about types these days. 3 parallel processing, it is not the nano optimization that matters but the simple use of e.g. map and reuse. Best is just run parallel behind the scenes if possible. 4 Hilvl data constructs like tree structure, e.g. finance and manufacturing. Anyone can make a long list of useful things, but these topics are large and hard to achieve, so I limit myself. When I retired I said I will devote some of my 'free' time to 'D' or 'Raku', I'm still looking for that time. At last, I hope you can have a fruitful cooperation with openD. You need to cooperate with everyone that can help.
Nov 21
On Saturday, 22 November 2025 at 07:22:56 UTC, Lars Johansson wrote:....If you're posting in this thread, it would benefit all, if you instead posted on the topic at hand. btw. There is value in disagreement; it is not orthogonal to working towards a shared goal.
Nov 21
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.htmlOkay, 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
Nov 27
On Thursday, 27 November 2025 at 19:41:18 UTC, Jonathan M Davis wrote:.. - Jonathan M DavisThe 'real' motivation seems clear to me: It's a proposal for a minimalist, pragmatic implementation of single-assignment semantics in D. By minimialist, it means getting single-assignment semantics without fundamentally restructuring D's type system. So final should remain a storage class. That final degrades to const in the presence of pointers/references, is the necessary tradeoff. As for compound assignment, for a user defined type it is just syntactical sugar for a function call (opOpAssign), which is a form of mutation, not re-assignment. Compound assignment on a primitive type suggests an intention to mutate, not reassign. So to maintain conceptual consistency, compound assignments should also be allowed on 'final' primitive types. Cases where you need single-assignment and mutable primitive types (like int) are rare, and may well be considered a niche use case, but I disagree that overall this would be a 'niche' feature. It is of course appropriate to do a thorough cost-benefit analysis of this feature, but I believe there are a sufficient number of cases for where a mutable, single-assignment variable would be very useful.
Nov 27
On Thursday, 27 November 2025 at 22:14:08 UTC, Peter C wrote:On Thursday, 27 November 2025 at 19:41:18 UTC, Jonathan M Davis wrote:The minimalist and pragmatic thing would be to not make it part of the language and let tooling handle it. Less work for everyone, core D devs don't have to do something and users don't have to learn and work with one more const-like keyword... - Jonathan M DavisThe 'real' motivation seems clear to me: It's a proposal for a minimalist, pragmatic implementation of single-assignment semantics in D.
Nov 27
On Friday, 28 November 2025 at 02:11:55 UTC, Kapendev wrote:On Thursday, 27 November 2025 at 22:14:08 UTC, Peter C wrote:When 'final' (or 'fixed' as I prefer it) appears, it documents intent and becomes part of the public contract. This is not something that should be left to a linter. It's a semantic guarantee, not just a style hint, and so needs to be a compile‑time contract.On Thursday, 27 November 2025 at 19:41:18 UTC, Jonathan M Davis wrote:The minimalist and pragmatic thing would be to not make it part of the language and let tooling handle it. Less work for everyone, core D devs don't have to do something and users don't have to learn and work with one more const-like keyword... - Jonathan M DavisThe 'real' motivation seems clear to me: It's a proposal for a minimalist, pragmatic implementation of single-assignment semantics in D.
Nov 27
On Friday, 28 November 2025 at 05:41:42 UTC, Peter C wrote:On Friday, 28 November 2025 at 02:11:55 UTC, Kapendev wrote:Example: ```d //+ fixed auto b = 420; b = 68 + 1; ``` ``` fixed-checker project/source Error(source/app.d:999): Can't change variable `b` with `+fixed` comment. ``` It's better to keep some things outside of the language sometimes just to keep things simple. Don't have a source, but I would say that people new to D already find `const` and `immutable` a bit confusing.On Thursday, 27 November 2025 at 22:14:08 UTC, Peter C wrote:When 'final' (or 'fixed' as I prefer it) appears, it documents intent and becomes part of the public contract. This is not something that should be left to a linter. It's a semantic guarantee, not just a style hint, and so needs to be a compile‑time contract.On Thursday, 27 November 2025 at 19:41:18 UTC, Jonathan M Davis wrote:The minimalist and pragmatic thing would be to not make it part of the language and let tooling handle it. Less work for everyone, core D devs don't have to do something and users don't have to learn and work with one more const-like keyword... - Jonathan M DavisThe 'real' motivation seems clear to me: It's a proposal for a minimalist, pragmatic implementation of single-assignment semantics in D.
Nov 27
On 28/11/2025 7:50 PM, Kapendev wrote:It's better to keep some things outside of the language sometimes just to keep things simple. Don't have a source, but I would say that people new to D already find `const` and `immutable` a bit confusing.Most of the time we have to describe the difference between headconst and transitive const anyway. If anything by having a variant of headconst it'll make it easier to explain. A lot of the confusion comes from transitive const being weaker than transitive immutable. Few languages have this. This proposal doesn't affect this.
Nov 27
On 11/27/2025 10:55 PM, Richard (Rikki) Andrew Cattermole wrote:A lot of the confusion comes from transitive const being weaker than transitive immutable. Few languages have this. This proposal doesn't affect this.Immutable is a great innovation in D.
Dec 03
On 11/27/2025 10:50 PM, Kapendev wrote:Don't have a source, but I would say that people new to D already find `const` and `immutable` a bit confusing.Immutable is solid code when dealing with multiple threads, as no synchronization is necessary. It also means no other pointers can modify it.
Dec 03
On 11/27/2025 6:11 PM, Kapendev wrote:The minimalist and pragmatic thing would be to not make it part of the language and let tooling handle it. Less work for everyone, core D devs don't have to do something and users don't have to learn and work with one more const-like keyword.You can utterly ignore `final` and your code will work just fine. That's also why `final` does not affect function overloading.
Dec 03
On 11/27/2025 2:14 PM, Peter C wrote:Compound assignment on a primitive type suggests an intention to mutate, not reassign. So to maintain conceptual consistency, compound assignments should also be allowed on 'final' primitive types.I agree with everything you wrote except that(!) I don't see a reason to accept compound assignment with a `final` variable.
Dec 03
On Wednesday, 3 December 2025 at 08:34:23 UTC, Walter Bright wrote:On 11/27/2025 2:14 PM, Peter C wrote:Actually I now disagree with my assertion as well ;-) Compound assignment is always a reassignment, not a mutation: final int i = 42; i += 10; // Am I just changing the value here? No. Step1: Calculate the right-hand side (RHS): i + 10 (which is 42 + 10 = 52). Step2: Assignment: Take the calculated value (52) and assign it back to the variable on the left-hand side (LHS): i = 52. Since the variable i was declared final, it is only allowed one assignment statement in its scope. The initial declaration final int i = 42; was the first assignment. The operation i = 52; is the second assignment, and thus, it directly violates the single-assignment rule enforced by final.Compound assignment on a primitive type suggests an intention to mutate, not reassign. So to maintain conceptual consistency, compound assignments should also be allowed on 'final' primitive types.I agree with everything you wrote except that(!) I don't see a reason to accept compound assignment with a `final` variable.
Dec 03
On Thursday, 27 November 2025 at 19:41:18 UTC, Jonathan M Davis wrote:... 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. ... - Jonathan M DavisHere is a 'simple' example of its value: class Data { int value = 1; } void main() { // const Data c = new Data(); //c.value = 42; // Error: cannot modify `const` expression `c.value` //c = new Data(); // Error: cannot modify `const` expression `c` c = new Data(); final Data c = new Data(); c.value = 42; // fine. c = new Data(); // Error (SAA): Cannot reassign a 'final' variable. // ..... // Pass the fixed reference to the performCheck function performCheck(c); // ..... // We have a guarantee that here, 'c' will still refer to the same object it was initialized with writeln("\nValue: ", c.value); } // The compiler guarantees that performCheck cannot // introduce a side effect by reassigning the reference. // void performCheck(final Data obj) { obj = new Data(); // Error (SAA): Cannot reassign a 'final' variable. obj.value = 1; // fine. (Mutating the object is allowed) }
Nov 27
On Thursday, 27 November 2025 at 22:23:33 UTC, Peter C wrote:On Thursday, 27 November 2025 at 19:41:18 UTC, Jonathan M Davis wrote:The compiler can already guarantee that, because the reference to c is passed by value. To pass it by reference and introduce such a side effect, the function would have to accept a ref Data.... 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. ... - Jonathan M DavisHere is a 'simple' example of its value: class Data { int value = 1; } void main() { // const Data c = new Data(); //c.value = 42; // Error: cannot modify `const` expression `c.value` //c = new Data(); // Error: cannot modify `const` expression `c` c = new Data(); final Data c = new Data(); c.value = 42; // fine. c = new Data(); // Error (SAA): Cannot reassign a 'final' variable. // ..... // Pass the fixed reference to the performCheck function performCheck(c); // ..... // We have a guarantee that here, 'c' will still refer to the same object it was initialized with writeln("\nValue: ", c.value); } // The compiler guarantees that performCheck cannot // introduce a side effect by reassigning the reference. // void performCheck(final Data obj) { obj = new Data(); // Error (SAA): Cannot reassign a 'final' variable. obj.value = 1; // fine. (Mutating the object is allowed) }
Nov 27
On Friday, 28 November 2025 at 00:40:06 UTC, Meta wrote:The compiler can already guarantee that, because the reference to c is passed by value. To pass it by reference and introduce such a side effect, the function would have to accept a ref Data.As I understand it, the scope of guarantee for 'final' (or fixed as I prefer to call it) is only inside the callee's scope. So, if you pass a 'final' variable into a function, the callee (the function) only sees the value/reference. The fact that the caller's variable is 'final' doesn’t constrain the callee in any way whatsoever. So there are only two options to ensure the constraint of the callee remains: void performCheck(final Data obj) // The parameter becomes a local variable holding a copy of the reference. { obj = new Data(); // Error (SAA): Cannot reassign a 'final' variable. obj.value = 1; } // Guarantee: Inside the function, all mutations apply to the same object the caller passed in. void performCheck(final ref Data obj) // The parameter is an alias for the caller’s variable itself. { obj = new Data(); // Error (SAA): Cannot reassign a 'final' variable. obj.value = 1; } // Guarantee: The caller's binding is formally protected, not just incidentally safe. Practically: Both forms ensure that mutations inside the function apply to the same object as c, thus ensuring the constraint of the callee. So.. back in main(): // We do indeed have a guarantee that here, 'c' will still refer to the same object it was initialized with: writeln("\nValue: ", c.value); Actually, now that I think about it, this example makes the case readonly). Otherwise, it's a pretty loose guarantee, and it becomes more about style and clarity than about strong enforcement across the call graph. A developer reading the code can’t be sure whether the “single assignment” promise holds everywhere or just in one scope! In my opinion, for it to be valuable, the compiler would need to enforce a "no rebinding anywhere" rule, which I think is outside the scope of this DIP? With transitivity, it becomes a strong, system-wide contract,
Nov 28
On Saturday, 29 November 2025 at 01:14:26 UTC, Peter C wrote:As I understand it, the scope of guarantee for 'final' (or fixed as I prefer to call it) is only inside the callee's scope. ....oops. I got it wrong: class Data { int value = 1; } void main() { final Data c = new Data(); // Establish a fixed storage location for the variable. c.value = 42; // ok fine. object itself is mutable, c = new Data(); // Error (SAA): Cannot reassign a 'final' variable. performCheck(c); // Caller guarantee: 'c' will still refer to the same object it was initialized with writeln("\nValue: ", c.value); } // According to the DIP: // "A ref cannot be taken of a final declaration:" // So the compiler would indeed catch this: void performCheck2(ref Data obj) "Error: Cannot pass a final variable as a ref parameter." { //.. } // An alternative (the standard, pass by value): // But, if the parameter obj is reassigned before mutation, the mutation will apply to a new, unintended object, thus breaking the caller's intent to have their original object modified. void performCheckAlt(Data obj) { obj = new Data(); obj.value = 42; // This results in a failure of the callers intent. } // Did it violate final c? No. The variable c was never reassigned. // Did it violate caller's intent? Yes. The caller expected the original object's value to change, but the function accidentally created and modified a different object. // The Solution: Constrain the Parameter: void performCheck_Safe(final Data obj) { obj = new Data(); // Error (SAA): Cannot reassign a 'final' variable. obj.value = 42; // Guarantee: mutation applies to the caller's object }
Nov 28
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. I've been refactoring my code to use single assignment, as it makes the code more readable.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.Of course, that also leaves the question of "op assignment"They're disallowed for `final` declarations.final int i = 42; i += 10; should be legal,I'm sorry, but the point is to make that illegal.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.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*)or simply disallowed. I hadn't thought of that case, though there are surely more cases I didn't think of!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.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.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.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.
Dec 03
On 03/12/2025 9:31 PM, Walter Bright wrote:On 11/27/2025 11:41 AM, Jonathan M Davis wrote:That'll already be the case for safe functions, but it shouldn't be disallowed for system. Typing it as const, also solves for fields. ```d struct Foo { final int* field; } struct Bar { int* field; } Foo foo; final Bar* bar; Bar zar; final Bar har; &foo.field; // const(int**) &bar.field; // int** &zar.field; // int** &har.field; // int** ``` I think I understand what we're miscommunicating on. Me and JMD are thinking its about changing of slots, not values or objects contained within. This weaker version isn't just a replication of const, that prevents method calls and other normal things. It'll be far more useful I suspect.Of course, that also leaves the question of "op assignment"They're disallowed for `final` declarations.final int i = 42; i += 10; should be legal,I'm sorry, but the point is to make that illegal.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*)or simply disallowed. I hadn't thought of that case, though there are surely more cases I didn't think of!
Dec 03
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: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.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.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
Dec 03
Initial implementation: https://github.com/dlang/dmd/pull/22171
Dec 01
On Tuesday, 2 December 2025 at 07:50:57 UTC, Walter Bright wrote:Initial implementation: https://github.com/dlang/dmd/pull/22171The (updated) DIP says:final has no effect on a ref declaration, as ref cannot be rebound.It would be more useful if `final ref` meant the pointed-to data was `final`. Indeed that's what your PR currently does: ```d int j; final ref fr = j; fr++; ``` ``` finalvar.d(25): Error: cannot modify `final fr` fr++; ^ ```
Dec 02
On Tuesday, 2 December 2025 at 07:50:57 UTC, Walter Bright wrote:Initial implementation: https://github.com/dlang/dmd/pull/22171Nice stuff. Thankyou for your effort here. I'll integrate the changes into my compiler .. and.. well.. we'll see what happens.
Dec 02
On Tuesday, 2 December 2025 at 07:50:57 UTC, Walter Bright wrote:Initial implementation: https://github.com/dlang/dmd/pull/22171With the changes, I was expecting an error here (which I didn't get): module mymodule; safe: private: import std; class Bag { final int[] items; // This is a binding-level guarantee - also acts an API invariant. this() { items = []; } } void main() { auto b = new Bag(); b.items ~= 1; // fine. still mutable. b.items = []; // was expecting an error here?? }
Dec 02
`final` for fields is not currently implemented with the PR. I have suspicion that it is unreasonable to implement it. The problem is that fields can overlap each other in messy ways (i.e. unions of structs). Trying to tease out finality in that soup may be not worth the bother.
Dec 03
On 03/12/2025 10:00 PM, Walter Bright wrote:`final` for fields is not currently implemented with the PR. I have suspicion that it is unreasonable to implement it. The problem is that fields can overlap each other in messy ways (i.e. unions of structs). Trying to tease out finality in that soup may be not worth the bother.Union's are system, I'm sure there will be something that can be done to make it ignored.
Dec 03
On Wednesday, 3 December 2025 at 04:05:28 UTC, Peter C wrote:
class Bag
{
final int[] items; // This is a binding-level guarantee -
also acts an API invariant.
this() { items = []; }
}
void main()
{
auto b = new Bag();
b.items ~= 1; // fine. still mutable.
b.items = []; // was expecting an error here??
}
Just to note that final on a dynamic array `a` should mean that
`a.ptr` and `a.length` never change, but elements e.g. `a[0]` can
change. So if final was allowed on fields, both assignments above
would error.
Dec 03
On Wednesday, 3 December 2025 at 10:18:50 UTC, Nick Treleaven wrote:On Wednesday, 3 December 2025 at 04:05:28 UTC, Peter C wrote:Yes, I forgot how dynamic arrays work. Dynamic arrays are allocated on the heap and will incur 'a reallocation' when their size changes (via ~= for example). So appending, removing, subsequently reserving a size, or subsequently reassigning explicately, should all be considered as a being disallowed. When you said "So if final was allowed on fields.." are you saying final does not work yet on class fields? Is that why I get no errors here?class Bag { final int[] items; // This is a binding-level guarantee - also acts an API invariant. this() { items = []; } } void main() { auto b = new Bag(); b.items ~= 1; // fine. still mutable. b.items = []; // was expecting an error here?? }Just to note that final on a dynamic array `a` should mean that `a.ptr` and `a.length` never change, but elements e.g. `a[0]` can change. So if final was allowed on fields, both assignments above would error.
Dec 03
On Wednesday, 3 December 2025 at 10:43:53 UTC, Peter C wrote:On Wednesday, 3 December 2025 at 10:18:50 UTC, Nick Treleaven wrote:This doesn't seem to be very useful. If you want a dynamic array that can't change length, why don't you use a static array?!?Just to note that final on a dynamic array `a` should mean that `a.ptr` and `a.length` never change, but elements e.g. `a[0]` can change. So if final was allowed on fields, both assignments above would error.Yes, I forgot how dynamic arrays work.
Dec 03
On Wednesday, 3 December 2025 at 10:53:04 UTC, Dom Disc wrote:This doesn't seem to be very useful. If you want a dynamic array that can't change length, why don't you use a static array?!?No, I wanted an array that *can* change length (i.e. a dynamic array), but where the variable holding the reference to that array cannot be reassigned. The actual problem is, a mismatch between my mental model (~= is mutation) and the implementation model. Conceptually, ~= is a mutation, but technically it's reassignment, because under-the-hood, when ~= is used, the variable's slice header is updated with a new reference (pointer + length), which the compiler treats as reassignment. So technically, what I want cannot be allowed. In essence, you can't have a 'final' dynamic array that still grows and shrinks. Instead, I'd need to implement some form of indirection (struct/class wrapper) to separate "mutation of the contents" from "rebinding of the slice header." Oh well. I expect there are many more mismatches (mental model vs implementation model), to be uncovered, because 'final' is not the most intuitive abstraction.
Dec 03
On Wednesday, 3 December 2025 at 10:53:04 UTC, Dom Disc wrote:On Wednesday, 3 December 2025 at 10:43:53 UTC, Peter C wrote:using System; using System.Collections.Generic; class Buffer { public readonly List<int> data = new List<int>(); } internal static class Program { static int Main() { var buf = new Buffer(); buf.data.Add(42); // error CS0191: A readonly field cannot be assigned to (except in a constructor or a variable initializer) //buf.data = new List<int>(); return 0; } }On Wednesday, 3 December 2025 at 10:18:50 UTC, Nick Treleaven wrote:This doesn't seem to be very useful. If you want a dynamic array that can't change length, why don't you use a static array?!?Just to note that final on a dynamic array `a` should mean that `a.ptr` and `a.length` never change, but elements e.g. `a[0]` can change. So if final was allowed on fields, both assignments above would error.Yes, I forgot how dynamic arrays work.
Dec 03
On Wednesday, December 3, 2025 3:53:04 AM Mountain Standard Time Dom Disc via dip.development wrote:This doesn't seem to be very useful. If you want a dynamic array that can't change length, why don't you use a static array?!?Well, the benefit would be basically the same as with a const dynamic array which is a slice of a mutable dynamic array except that you can mutate the elements. The length is dynamically determined (whereas the length of a static array is known at compile time), and the elements don't need to be copied. Of course, if you're talking about an array whose length is known at compile time, you're creating it in that function, and it's not going to escape that function, then you probably should use a static array to avoid allocating memory, but in many cases, the dynamic array could be coming from elsewhere (e.g. via a function parameter), and you simply don't want to mutate its ptr or length. Now, I personally don't think that it's worth adding a storage class to prevent mutating the ptr or length fields of a dynamic array, but using a static array instead would have very different semantics to simply not mutating the ptr or length of a dynamic array within a function. Personally, if I wanted to use a dynamic array without mutating its ptr or length (which I'm sure that I've done plenty of times before), then I just wouldn't mutate the ptr or length. - Jonathan M Davis
Dec 03









Walter Bright <newshound2 digitalmars.com> 