digitalmars.dip.development - First Draft: Static Single Assignment
- Walter Bright (1/1) Nov 14 2025 https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assi...
- Richard (Rikki) Andrew Cattermole (3/5) Nov 15 2025 Where is the draft? This is only a link to an ideas thread. No new
- Walter Bright (1/1) Nov 15 2025 Oops! https://github.com/WalterBright/documents/blob/master/final.md
- Peter C (9/11) Nov 17 2025 Is there a case for allowing the responsibility for upholding the
- Walter Bright (1/1) Nov 19 2025 I'd say "no". Just delete the final keyword.
- Peter C (19/21) Nov 22 2025 In C#, in order to guarantee thread safety and ensure safe object
- Walter Bright (4/4) Nov 24 2025 Constructors in D are allowed to initialize fields that would be otherwi...
- Juraj (20/28) Dec 05 2025 But in this thread
- Walter Bright (3/4) Nov 15 2025 Ooops! forgot the linky:
- Nick Treleaven (15/16) Nov 16 2025 ```d
- monkyyy (3/5) Nov 16 2025 if its not `final int*` it would be repeating the ref type theory
- Walter Bright (3/9) Nov 16 2025 Fortunately, `final` is a storage class and not a type, so type theory d...
- monkyyy (17/27) Nov 16 2025 Type theory is in laws of physics, length * time = mph; square
- Walter Bright (6/6) Nov 19 2025 What you're proposing is "head const", which is what C and C++ have. D h...
- Dukc (10/16) Nov 24 2025 What issues does it cause that this DIP avoids? Even this DIP
- Walter Bright (2/10) Nov 24 2025 It won't allow a pointer to the field.
- Dukc (6/17) Nov 24 2025 I don't understand. The compiler would not allow a pointer to
- Walter Bright (2/7) Dec 02 2025 final is not part of the type system, const is.
- Walter Bright (2/21) Nov 16 2025 Great questions! I will think about it.
- Walter Bright (5/22) Nov 16 2025 ```d
- Peter C (11/36) Nov 16 2025 The auto keyword should properly deduce the full declaration of
- Peter C (9/12) Nov 17 2025 No. I'm wrong, and I better correct myself.
- Walter Bright (1/1) Nov 23 2025 Correctamundo
- Nick Treleaven (7/28) Nov 17 2025 I think it needs to be `const(int)*` to protect `f` from ever
- Peter C (12/17) Nov 17 2025 Again, I have to correct my thinking, and agree with you here.
- jmh530 (9/25) Nov 17 2025 Would it make sense to make final/head-const part of the type
- Peter C (15/23) Nov 17 2025 Wouldn't this change the re-engineering scope of final from
- Peter C (17/19) Nov 16 2025 'final' (or 'init' as I prefer to call it) is a single-assignment
- Peter C (20/25) Nov 16 2025 int i;
- Quirin Schroll (66/70) Nov 18 2025 It were much more useful as a qualifier. If it were a qualifier,
- jmh530 (15/18) Nov 18 2025 I can't say I was following all of what you were saying with
- Tim (14/19) Nov 18 2025 For full C++ interop it would also be needed for the hidden
- jmh530 (2/18) Nov 18 2025 Good point.
- jmh530 (9/29) Nov 18 2025 Similarly, assuming this DIP is approved as-is, a newcomer to the
- Peter C (21/31) Nov 18 2025 Ummm... it's a final method.
- jmh530 (4/25) Nov 19 2025 My point was that if this DIP is approved, then a newbie to the
- Nick Treleaven (3/12) Nov 19 2025 That's the same issue as `const int f() {}`, although at least
- Nick Treleaven (8/21) Nov 18 2025 I think the `this` reference is already essentially `final` in a
- Tim (3/9) Nov 19 2025 Yes, this is only relevant for structs.
- Walter Bright (2/2) Nov 23 2025 What we'd like to do is make the 'this' parameter explicit on member fun...
- Richard (Rikki) Andrew Cattermole (16/18) Nov 23 2025 We'll need to be very careful with this line of thinking:
- Walter Bright (3/7) Dec 03 2025 We already disambiguate it for other attributes, which is why I think ab...
- Peter C (60/61) Nov 18 2025 Consider this to be my complete rejection of further overloading
- Walter Bright (10/10) Nov 23 2025 Ironically, C++ head const is routinely used "as if" it was transitive c...
- Paul Backus (23/30) Nov 27 2025 Now, consider their effect on the following code:
- Meta (5/38) Nov 27 2025 Ya, it introduces all sorts of complications. Hence my suggestion
- Walter Bright (1/1) Dec 02 2025 Yup. If it is not enforced, it is as worthless as C++ 'const' is.
- Walter Bright (9/9) Dec 02 2025 You are correct in the behavior.
- Paul Backus (13/24) Dec 02 2025 I agree that making final a full-fledged type qualifier would add
- Jonathan M Davis (19/23) Dec 03 2025 I agree with this 100%. What's proposed here seems like a mess of specia...
- Walter Bright (2/2) Dec 04 2025 I appreciate your sentiment, Jonathan, but all I can say is give it a tr...
- Jonathan M Davis (21/23) Dec 04 2025 Honestly? I'm already of the opinion at this point that const is almost
- Walter Bright (8/8) Dec 05 2025 You're quite correct in that you can avoid const and immutable completel...
- Peter C (5/8) Dec 05 2025 Isn't that what 'scopeprivate' does? (except at a much larger
- Peter C (15/19) Dec 05 2025 In the end, 'final as a storage class, is just too weak, as you
- Walter Bright (9/18) Dec 04 2025 It does what it says on the cereal box it does - you cannot change the v...
- Paul Backus (11/22) Dec 04 2025 Well, no, the complications come as soon as you try to pass it to
- Walter Bright (3/3) Dec 05 2025 It's true that there's not much point to using `final` to replace `const...
- Walter Bright (3/6) Dec 04 2025 The C++ reference type is part of the type system, and I definitely work...
- Richard (Rikki) Andrew Cattermole (4/4) Nov 15 2025 Three things I'd like to see here:
- monkyyy (13/17) Nov 15 2025 ```d
- Peter C (22/43) Nov 15 2025 This use of 'final' .. me no like!
- Kapendev (4/6) Nov 16 2025 I don't think this is a good idea because `init` is a common
- Peter C (9/16) Nov 16 2025 We've had the 'init' keyword in C# for many years now.
- Jonathan M Davis (14/31) Nov 18 2025 1. init is already used in D by the language itself to give the value th...
- Peter C (11/51) Nov 18 2025 It's highly unlikely that 'init' or 'fixed' (which is now my new
- Jonathan M Davis (5/11) Nov 19 2025 D does not have contextual keywords, and Walter has rejected the idea wh...
- Peter C (3/16) Nov 19 2025 yeepers! well.. good luck with it then ;-)
- Walter Bright (1/1) Nov 21 2025 `final` is better anyway.
- jmh530 (3/4) Nov 21 2025 ...is it possible to edit your configs so that people on the web
- Walter Bright (2/4) Dec 03 2025 If you use the threaded view it shouldn't be a problem?
- Peter C (15/16) Nov 21 2025 not to me it isn't:
- Peter C (20/21) Nov 29 2025 // good luck interpreting this code...(or explaining it to
- Meta (4/17) Nov 19 2025 That's not entirely true. `body` became a contextual keyword
- Walter Bright (5/8) Nov 19 2025 Wweeeellll, there is some flexibility there:
- Peter C (47/56) Nov 29 2025 Again, a *contextual* keyword like 'fixed' would solve the issue
- Walter Bright (4/9) Nov 19 2025 Problematic, because one can modify it with some pointer manipulation
- Richard (Rikki) Andrew Cattermole (4/9) Nov 20 2025 You can do this with stack variables also in @system code.
- Peter C (22/26) Nov 21 2025 If you step outside the safety fence, then the problem is you,
- Peter C (25/35) Nov 21 2025 and forloops?
- Peter C (33/33) Nov 21 2025 On Friday, 21 November 2025 at 09:48:57 UTC, Peter C wrote:
- Richard (Rikki) Andrew Cattermole (3/48) Nov 21 2025 Variables defined in a loop get extracted outside of it.
- Peter C (29/34) Nov 21 2025 But aren't you refering to a performance optimization?
- user1234 (24/25) Nov 17 2025 two notes:
- Nick Treleaven (8/30) Nov 17 2025 The DIP example shows that non-mutable ref to a final variable is
- user1234 (4/38) Nov 17 2025 Alright. Maybe a clarification like "a final parameter can never
- Walter Bright (1/1) Nov 19 2025 I don't think these will be a problem. Your examples all should pass.
- Walter Bright (8/15) Nov 23 2025 This should be an error. It is equivalent to:
- Lars Johansson (30/31) Nov 21 2025 Hi all,
- Peter C (6/7) Nov 21 2025 If you're posting in this thread, it would benefit all, if you
- Jonathan M Davis (65/66) Nov 27 2025 Okay, what's the real motivation here? If it's to prevent assignment, th...
- Peter C (24/26) Nov 27 2025 The 'real' motivation seems clear to me: It's a proposal for a
- Kapendev (5/13) Nov 27 2025 The minimalist and pragmatic thing would be to not make it part
- Peter C (6/20) Nov 27 2025 When 'final' (or 'fixed' as I prefer it) appears, it documents
- Kapendev (17/38) Nov 27 2025 Example:
- Richard (Rikki) Andrew Cattermole (8/11) Nov 27 2025 Most of the time we have to describe the difference between headconst
- Walter Bright (2/4) Dec 03 2025 Immutable is a great innovation in D.
- Walter Bright (3/5) Dec 03 2025 Immutable is solid code when dealing with multiple threads, as no
- Walter Bright (3/6) Dec 03 2025 You can utterly ignore `final` and your code will work just fine. That's...
- Walter Bright (3/6) Dec 03 2025 I agree with everything you wrote except that(!) I don't see a reason to...
- Peter C (16/23) Dec 03 2025 Actually I now disagree with my assertion as well ;-)
- Peter C (32/40) Nov 27 2025 Here is a 'simple' example of its value:
- Meta (4/46) Nov 27 2025 The compiler can already guarantee that, because the reference to
- Walter Bright (20/61) Dec 03 2025 The motivation is to find a way to express "single assignment".
- Richard (Rikki) Andrew Cattermole (25/50) Dec 03 2025 That'll already be the case for @safe functions, but it shouldn't be
- Walter Bright (10/10) Dec 04 2025 I've come to realize the following:
- Paul Backus (4/14) Dec 04 2025 So now we've created a situation where `typeof(x)*` and
- Walter Bright (9/11) Dec 04 2025 Perhaps. I'm reminded of how name lookups work in D. It originally was v...
- jmh530 (3/13) Dec 04 2025 So taking the address of x gives a const pointer? Is `x` itself
- Peter C (40/56) Dec 04 2025 final is a storage a class. const is a type qualifier.
- Walter Bright (1/1) Dec 04 2025 Thank you for the detailed explanation of this.
- Walter Bright (2/3) Dec 04 2025 The address of a `final` variable will be a pointer to const.
- Jonathan M Davis (59/69) Dec 03 2025 If what you want is truly single assignment, then that's not a question ...
- Walter Bright (13/24) Dec 04 2025 I cannot see why `final int x = 3; ++x;` should be legal.
- Richard (Rikki) Andrew Cattermole (15/21) Dec 04 2025 Why would we use this, if we have to annotate methods as final?
- Jonathan M Davis (60/76) Dec 04 2025 That would depend on the goal. If the goal is truly single assignment, t...
- Peter C (21/30) Dec 05 2025 For a primitive type, there is no separate internal structure to
- Quirin Schroll (80/165) Jan 16 That might be a fad. Something isn’t good because it’s popular.
- Walter Bright (2/2) Dec 01 2025 Initial implementation:
- Nick Treleaven (14/18) Dec 02 2025 It would be more useful if `final ref` meant the pointed-to data
- Peter C (4/6) Dec 02 2025 Nice stuff. Thankyou for your effort here.
- Peter C (19/21) Dec 02 2025 With the changes, I was expecting an error here (which I didn't
- Walter Bright (4/4) Dec 03 2025 `final` for fields is not currently implemented with the PR. I have susp...
- Richard (Rikki) Andrew Cattermole (3/9) Dec 03 2025 Union's are @system, I'm sure there will be something that can be done
- Walter Bright (3/3) Dec 04 2025 You may very well be correct. But my current efforts are to make it work...
- Nick Treleaven (5/17) Dec 03 2025 Just to note that final on a dynamic array `a` should mean that
- Peter C (11/29) Dec 03 2025 Yes, I forgot how dynamic arrays work.
- Dom Disc (3/10) Dec 03 2025 This doesn't seem to be very useful. If you want a dynamic array
- Peter C (20/23) Dec 03 2025 No, I wanted an array that *can* change length (i.e. a dynamic
- Peter C (20/32) Dec 03 2025 Essentially I was trying to do this in D (below is C# though)
- Jonathan M Davis (19/21) Dec 03 2025 Well, the benefit would be basically the same as with a const dynamic ar...
- Walter Bright (2/4) Dec 04 2025 I've made no attempt on that yet.
- Peter C (4/6) Dec 05 2025 This should be allowed:
- Juraj (5/10) Dec 05 2025 I suggest keeping the topic about D and not a hypothetical
- Peter C (9/20) Dec 05 2025 I'm pretty sure I *was* talking about D...
- Walter Bright (3/6) Dec 05 2025 Oh well!
- Peter C (5/11) Dec 05 2025 Only assignment uses the assignment operator -> '='!
- Jonathan M Davis (26/40) Dec 05 2025 Well, if we want to get technical, talking about "static single assignme...
- Peter C (21/71) Dec 06 2025 Then I stand corrected.
- Peter C (12/23) Dec 05 2025 also, if final is ever going to work with class fields, then if
- Nick Treleaven (3/14) Dec 06 2025 No, read https://dlang.org/spec/class.html#field-init. That's
- Peter C (11/27) Dec 06 2025 So, 'technically' I stand corrected again.
- monkyyy (9/10) Dec 05 2025 if its not going to be part of the type system it shouldnt exist;
- Peter C (12/23) Dec 05 2025 Not every safety feature has to be a deep type-system construct.
- Kapendev (9/35) Dec 06 2025 It's impossible to focus when you don't say what you believe with
- Nick Treleaven (5/7) Dec 06 2025 I don't think that's possible - making a struct Final which acts
- monkyyy (5/12) Dec 06 2025 Does the compilers build chain break my favorite compiler bugs?
- Nick Treleaven (19/26) Dec 10 2025 About the UDA - that would require some mechanism to enforce it.
- monkyyy (82/115) Jan 13 static assert exists, given any `isFoo` template, it can be
- Richard (Rikki) Andrew Cattermole (25/25) Dec 05 2025 I've been having a bit of trouble putting into words what I want to say
- Peter C (40/41) Dec 05 2025 The real problem is: the proposal for a 'storage-class' final is
- Peter C (28/30) Dec 05 2025 What a true type‑level fixed annotation would look like if it
- Walter Bright (11/12) Dec 06 2025 My implementation isn't going too well. I keep finding more and more spe...
- monkyyy (23/26) Dec 07 2025 I suggest maybe this should fail:
- Paul Backus (2/11) Dec 07 2025 I guess all I can say is, I tried to warn you.
https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.html
Nov 14 2025
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 2025
Oops! https://github.com/WalterBright/documents/blob/master/final.md
Nov 15 2025
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 2025
I'd say "no". Just delete the final keyword.
Nov 19 2025
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 2025
Constructors in D are allowed to initialize fields that would be otherwise disallowed. ensuring it is not particularly relevant to D.
Nov 24 2025
On Sunday, 16 November 2025 at 01:50:32 UTC, Walter Bright wrote:Oops! https://github.com/WalterBright/documents/blob/master/final.mdIn the DIP:final can be applied to struct and class fields.But in this thread (<https://forum.dlang.org/post/10gou85$2p0e$1 digitalmars.com>) :`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.IMO, this should be put into the DIP (at least as a note), otherwise people may get the wrong idea about what this feature enables. For me personally, this whole thing does not looks like its worth the bother. The examples in the DIP and in the PR tests show nothing I would not use `const` if I was getting my code in a state where I fear re-assign. The motivation behind this is more readable code, but adds ton of cognitive load: As I understand it, final will match on non-`cost` parameters, but instead `ref`, it will match `const ref`. That will be confusing. But my main worry is, that the DIP process will be sidestepped, as this feature can be presented as a pure addition that does not break old code. I hope this will not be the case.
Dec 05 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
Yup. If it is not enforced, it is as worthless as C++ 'const' is.
Dec 02 2025
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 2025
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 2025
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 2025
I appreciate your sentiment, Jonathan, but all I can say is give it a try and see how it feels. I bet you'll like it! I know I'm looking forward to using it.
Dec 04 2025
On Thursday, December 4, 2025 6:35:00 PM Mountain Standard Time Walter Bright via dip.development wrote:I appreciate your sentiment, Jonathan, but all I can say is give it a try and see how it feels. I bet you'll like it! I know I'm looking forward to using it.Honestly? I'm already of the opinion at this point that const is almost always a waste of time and that immutable is really only of value, because it helps write thread-safe code. There's no way that I'm going to be using a storage class just to try to catch when a variable gets assigned to or mutated. If I thought that that had much value, I'd already be using const in most such cases, whereas in practice, I've found that const just actively gets in the way (particularly with user-defined types, and pretty clearly, for structs - which I use _far_ more than classes - final won't be any different). const is much less of an issue with primitive types, but I'm also not sure that I've _ever_ caught a bug in code thanks to const. So, I've gone from being really glad that D had const unlike Java to coming to the conclusion that const's only real value is in making it so that the same piece of code can use a mutable and immutable object, and aside from virtual functions, that can generally be done just fine with a templated function. So, if folks want to use final, that's fine (and clearly, some folks like the idea), but I truly don't see the value. It just seems like yet another complication being added to the language while not really doing much. And D is already a very complex language. - Jonathan M Davis
Dec 04 2025
You're quite correct in that you can avoid const and immutable completely if you prefer. C and C++ have amply proven that. Where they shine, however, is as compiler-enforced contracts restricting what can be done with data. I find them more and more useful the more complex and larger a program becomes. `final` is in the same boat. `final` won't make your code run any faster or better. What it does do is tell the person reading the code that nobody is poking at it somewhere else in the function.
Dec 05 2025
On Saturday, 6 December 2025 at 00:43:25 UTC, Walter Bright wrote:... What it does do is tell the person reading the code that nobody is poking at it somewhere else in the function.Isn't that what 'scopeprivate' does? (except at a much larger code level.. the module). On your own argument, 'scopeprivate' deserves more consideration than 'final'.
Dec 05 2025
On Friday, 5 December 2025 at 06:48:47 UTC, Jonathan M Davis wrote:.. It just seems like yet another complication being added to the language while not really doing much. And D is already a very complex language. - Jonathan M DavisIn the end, 'final as a storage class, is just too weak, as you point out. Implemented instead, as 'fixed' in the type system itself, is surely the more elegant solution. But it is probably not justified for D's current user base, unless the majority of D users hit rebinding bugs often enough to demand systemic guarantees. Any if rebinding bugs aren't common enough to justify a type‑level solution, they certainly aren't common enough to justify a weaker storage‑class band‑aid. So I expect 'final as a storage class', or 'fixed as a type-level feature', are both likely to not gain much further traction. So I'll turn my attention to something else now ;-)
Dec 05 2025
On 12/2/2025 3:04 PM, Paul Backus wrote: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.It does what it says on the cereal box it does - you cannot change the value of a final declaration once it is initialized. The complications come from taking the address of a final variable. I doubt this will be a common usage pattern. I suppose we could delete that behavior, but I want to see first if we can use it productively.This is the exact kind of confusing dessert topping/floor wax duality that you (rightly) criticize in C++'s reference types.I recall the dessert topping / floor wax was about a struct could be both a value type and a reference type.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.We can disagree on what is 'right' !!
Dec 04 2025
On Friday, 5 December 2025 at 01:18:44 UTC, Walter Bright wrote:On 12/2/2025 3:04 PM, Paul Backus wrote:Well, no, the complications come as soon as you try to pass it to a function, as I explained in my earlier message: https://forum.dlang.org/post/fpgcyzteuikxblkuwxkd forum.dlang.org Functions that overload on `ref` and `const ref` are a common usage pattern. Functions with an `inout ref` parameter are a common usage pattern. Template functions with an `auto ref T` parameter are a common usage pattern. If we are not willing or able to implement `final` in a way that supports these usage patterns, then we would be better off without it.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.It does what it says on the cereal box it does - you cannot change the value of a final declaration once it is initialized. The complications come from taking the address of a final variable. I doubt this will be a common usage pattern. I suppose we could delete that behavior, but I want to see first if we can use it productively.
Dec 04 2025
It's true that there's not much point to using `final` to replace `const` for a non-pointer type. It's more useful in the role of head-const.
Dec 05 2025
On Saturday, 6 December 2025 at 00:48:19 UTC, Walter Bright wrote:It's true that there's not much point to using `final` to replace `const` for a non-pointer type. It's more useful in the role of head-const.ok. now we can move to the more interesting stuff, beyond primitive types, since: final int x = 42; is semantically *identical* to: const int x = 42; That's because primitive values are inherently immutable - i.e a mutation on a primitive type is actually a re-assignment. That is, you are overwriting the storage bound to a variable with a new value. Now allowing two different keywords to express the same thing on these types could be seen as redundancy or even syntactic noise. Therefore final seems to provide no additional expressiveness on primitive types. One could argue therefore, that 'final' should not be allowed on primitive types, for the reason that it is essentially the exact same as const. A counter argument would be, that 'final' is not saying anything about 'object immutability', and that primitive immutability is already inherent, because there is no indirection. So even though 'final' exactly duplicates 'const' for primitive types, consistency and uniformity matter more. That is, when someone writes: final T x = ...; they expect it to work for every T. So for those who will use 'final' on primitive types, you're doing it because you are want to explicately state that the binding is immutable. If you want instead wanted to state that the value is immutable, just use const. The semantics are the same, but the intention is different. On non-primitive types, final stops being a trivial redundancy and becomes a meaningful design and safety tool! final can protect a reference to an object, with forcing immutablilty on the object itself. final Foo f = new Foo(); f = new Foo(); // oops, lost the original object. vs: final Foo f = new Foo(); f = new Foo(); // Error: cannot modify `final f` Anyone who argues that 'final' is a useless or unnecessary safety guarantee does not understand programming. Accidental rebinding is a common source of subtle, hard-to-find bugs in object-oriented programming. In large systems, protecting the reference can prevent far more serious bugs than controlling object mutability alone.
Dec 05 2025
On Saturday, 6 December 2025 at 04:16:59 UTC, Peter C wrote:..Correction to previous post: "final can protect a reference to an object, **without** forcing immutablilty on the object itself.
Dec 05 2025
On Saturday, 6 December 2025 at 04:19:12 UTC, Peter C wrote:On Saturday, 6 December 2025 at 04:16:59 UTC, Peter C wrote:argH! cannot edit posts!!! another correction: Foo f = new Foo(); f = new Foo(); // oops, lost the original object. vs: final Foo f = new Foo(); f = new Foo(); // Error: cannot modify `final f`..Correction to previous post: "final can protect a reference to an object, **without** forcing immutablilty on the object itself.
Dec 05 2025
On 12/2/2025 3:04 PM, Paul Backus wrote:This is the exact kind of confusing dessert topping/floor wax duality that you (rightly) criticize in C++'s reference types.The C++ reference type is part of the type system, and I definitely work hard to avoid that sand trap.
Dec 04 2025
Three things I'd like to see here: 1. Function parameters 2. Fields 3. Globals
Nov 15 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
I don't think these will be a problem. Your examples all should pass.
Nov 19 2025
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 2025
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 2025
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 2025
On Saturday, November 15, 2025 12:13:13 AM Mountain Standard Time Walter Bright via dip.development wrote:https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
I've come to realize the following:
```d
void test() {
final int x = 3;
pragma(msg, typeof(&x));
}
```
prints: const(int)*
which is a more practical way to make this all work. I've made the changes to
the PR.
Dec 04 2025
On Thursday, 4 December 2025 at 08:58:03 UTC, Walter Bright wrote:
I've come to realize the following:
```d
void test() {
final int x = 3;
pragma(msg, typeof(&x));
}
```
prints: const(int)*
which is a more practical way to make this all work. I've made
the changes to the PR.
So now we've created a situation where `typeof(x)*` and
`typeof(&x)` are different. Surely nobody will find that
confusing. :)
Dec 04 2025
On 12/4/2025 5:34 AM, Paul Backus wrote:So now we've created a situation where `typeof(x)*` and `typeof(&x)` are different. Surely nobody will find that confusing. :)Perhaps. I'm reminded of how name lookups work in D. It originally was very simple - the scopes were nested and lookup starts at the innermost one and progresses outward. Every single person told me that was confusing. Even Andrei! So we now have multiple phases of lookups in a difficult-to-explain and clumsy to implement system. And nobody complains about it, calling it "intuitive". This is what makes computer language design a bit of a black art rather than a logical mathematical construction.
Dec 04 2025
On Thursday, 4 December 2025 at 08:58:03 UTC, Walter Bright wrote:
I've come to realize the following:
```d
void test() {
final int x = 3;
pragma(msg, typeof(&x));
}
```
prints: const(int)*
which is a more practical way to make this all work. I've made
the changes to the PR.
So taking the address of x gives a const pointer? Is `x` itself
const?
Dec 04 2025
On Thursday, 4 December 2025 at 15:18:18 UTC, jmh530 wrote:On Thursday, 4 December 2025 at 08:58:03 UTC, Walter Bright wrote:final is a storage a class. const is a type qualifier. The final storage class does not modify the base type of the variable. The type of the variable x is still the underlying type - in this case, an int. Although the variable's type isn't const(int), the compiler has to guarantee that a final variable cannot be modified. Therefore, when you take its address (&x), the resulting pointer must be a pointer to a read-only object to prevent modification through the pointer. The compiler implicitly adds const to the pointer's target type. That final degrades to const in the presence of pointers/references, is the necessary tradeoff for getting the single assignment guarantee without having to change the type system itself. void test() { // const vs final const int z = 10; writeln("z is a ", typeof(z).stringof); // z is a const(int) writeln("&z is a ", typeof(&z).stringof); // &z is a const(int)* auto y = &z; writeln("y is a ", typeof(y).stringof); // y is a const(int)* writeln("&y is a ", typeof(&y).stringof); // &y is a const(int)** writeln; // All pointers derived from an expression involving a final variable // must point to const data to maintain the promise of single assignment. final int x = 3; writeln("x is a ", typeof(x).stringof); // x is a int writeln("&x is a ", typeof(&x).stringof); // &x is a const(int)* auto r = &x; writeln("r is a ", typeof(r).stringof); // r is a const(int)* writeln("&r is a ", typeof(&r).stringof); // &r is a const(int)** }I've come to realize the following: ```d void test() { final int x = 3; pragma(msg, typeof(&x)); } ``` prints: const(int)* which is a more practical way to make this all work. I've made the changes to the PR.So taking the address of x gives a const pointer? Is `x` itself const?
Dec 04 2025
Thank you for the detailed explanation of this.
Dec 04 2025
On 12/4/2025 7:18 AM, jmh530 wrote:So taking the address of x gives a const pointer? Is `x` itself const?The address of a `final` variable will be a pointer to const.
Dec 04 2025
On Wednesday, December 3, 2025 1:31:42 AM Mountain Standard Time Walter Bright via dip.development wrote:On 11/27/2025 11:41 AM, Jonathan M Davis wrote: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 2025
On 12/3/2025 5:10 PM, 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.I cannot see why `final int x = 3; ++x;` should be legal. I have not attempted to work through exactly how final would affect struct fields. So I'm not prepared with a solid opinion on it. But even if it does not work for fields, it remains useful for variables.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.It brings zero benefits to code generation. Its purpose is to make the code more understandable. Many, many times I will do a search to see if a variable I set at the beginning of a long function is modified in the guts a page away. I've read multiple accounts online in other languages that people like this diagnostic capability.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.Unlike C++, structs and classes are completely different. A final on a class would affect only the class reference. A final on a struct will only affect its fields.
Dec 04 2025
On 04/12/2025 10:06 PM, Walter Bright wrote:
On 12/3/2025 5:10 PM, 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.
I cannot see why |final int x = 3; ++x;| should be legal.
Why would we use this, if we have to annotate methods as final?
```d
struct Foo {
int field;
void method() final {
field = 2; // Error
}
}
final Foo foo;
foo.method;
```
That's const minus the type qualifier.
This capability is what separates this from const, being able to mutate
the contents of the cell.
Dec 04 2025
On Thursday, December 4, 2025 2:06:34 AM Mountain Standard Time Walter Bright via dip.development wrote:On 12/3/2025 5:10 PM, Jonathan M Davis wrote:That would depend on the goal. If the goal is truly single assignment, then x = 42; would violate that, but ++x; would not, because it's not an assignment. It's mutation, yes, but it's not assignment. On the other hand, if the actual goal is head-const, then ++x; would obviously violate that, because there is no indirection, and head-const is no different from const in such a situation.If what you want is truly single assignment, then that's not a question of mutation. It's a question of assignment.I cannot see why `final int x = 3; ++x;` should be legal.I have not attempted to work through exactly how final would affect struct fields. So I'm not prepared with a solid opinion on it. But even if it does not work for fields, it remains useful for variables.The problem is that if putting final on a variable of a struct type is supposed to act like head-const, that's not possible if final is not a type qualifier. There's simply no mechanism for the compiler to validate that a member function is going to treat the member variables as head-const (at least not when the compiler can't assume that it has full access to the source code for the struct). It can treat a final struct variable as full-on const and guarantee that by disallowing calling any member functions which aren't const or inout, but for code such as final MyStruct m; m.foo(); to have any guarantees, it has to be functionally equivalent to const MyStruct m; m.foo(); because there is no mechanism for marking a member function as final (at least not in this sense, though obviously, final can be used on class member functions for what it currently means in D). Of course, the situation isn't quite the same if a struct's member variable is explicitly marked as final, because then that's a question of verifying the implementation rather than any code using the struct caring, so that's a separate discussion, but if a variable of the struct's type is marked final, then that implies that all of the member variables are marked as final, and that has to be guaranteed even when the source code isn't available, so that means that the validation is left to the function signatures, and that means treating final the same as const with regards to structs. For classes, the situation can be different because of the implicit indirection, but struct's don't have that. So, from what I can see, final is going to be utterly useless with variables which are structs. Depending on the rules for member variables, maybe it would still have some value when marked explicitly on a member variable, but validating that is different, because then it's the member function implementations which need to be validated, and so the compiler is guaranteed to have the source code when it does those checks, whereas with a struct variable, the validation has to be possible with just the function signatures themselves.Yes, and as I've tried to point out, that makes final no different from const when it's used on a variable that's a struct. And maybe that's acceptable, but without final being a type qualifier, I don't see how we can do better than that. But either way, my point about generic code is that because structs will have to behave differently from classes with regards to final, using final in generic code risks being problematic. Depending on the specifics, it could easily result in it being difficult to write generic code where final works with both classes and structs. However, I couldn't really say how bad it would be in practice without dealing with real world code that attempted to use final in generic code. But given how problematic const already tends to be in generic code, I don't know how much sense it would make to use final in generic code anyway. - Jonathan M DavisI 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.Unlike C++, structs and classes are completely different. A final on a class would affect only the class reference. A final on a struct will only affect its fields.
Dec 04 2025
On Friday, 5 December 2025 at 06:32:40 UTC, Jonathan M Davis wrote:.. If the goal is truly single assignment, then x = 42; would violate that, but ++x; would not, because it's not an assignment. It's mutation, yes, but it's not assignment. .. - Jonathan M DavisFor a primitive type, there is no separate internal structure to 'mutate'. For a primitive type, the assignment is the operation that changes the state. - The processor fetches the current value (42). - It calculates the new value (43). - It stores the new value (43) back into the memory location designated by x. So, the only way to change the state of a primitive variable, is to write a new value into its memory location. This act of writing a new value is, by definition, an assignment operation, which is a violation of the constraint, so the compiler must reject it. Primitives: The rule [indirectly] results in immutability (same as const) - but immutability is the consequence, not the intention. Objects: The rule forces the reference to be single-assigned, allowing for mutation of the object's internal state (different from const).
Dec 05 2025
On Wednesday, 3 December 2025 at 08:31:42 UTC, Walter Bright wrote:On 11/27/2025 11:41 AM, Jonathan M Davis wrote:That might be a fad. Something isn’t good because it’s popular. The value of a thing must be established independent of its popularity.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.That’s a style. A linter is the tool to enforce a style. A linter can be configured to ban things that a compiler shouldn’t ever ban. In this case, a team might decide: When a function is more than 25 lines of code long (counting all lines), then enforce single assignment, except for variables that are lint-annotated to be mutable. There can be project-specific exceptions that make sense. It’s a much more flexible approach and does not burden the compiler and requires no thorough type system analysis. It doesn’t introduce core-language corner cases. Everyone knows linters are best-effort and won’t catch all problems.Yes, and everyone knows `const` bans modification, not just assignment.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.What about a class type? For classes, there’s assignment of the handle and `opAssign`. They look identical in code, but they’re semantically very different. You could argue that class `opAssign` isn’t modification in the sense of `final` because it’s akin to assigning the pointed-to `int` of an `int*`.Of course, that also leaves the question of "op assignment"They're disallowed for `final` declarations.```d final int i = 42; i += 10; ``` should be legal,I'm sorry, but the point is to make that illegal.So, it can only call `const` member functions? At that point, it can be just `const`.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.At least at this point, you should stop and re-assess.Of course, the other issue here is that because `final` is a storage class and not a type qualifier, things are going to degrade to `const` pretty quickly with pointers and references. For instance, ```d final int i = 42; auto ptr = &i; ``` is going to have to make `ptr` `const(int)*` or `const(int*)`or simply disallowed. I hadn't thought of that case, though there are surely more cases I didn't think of!The problem is that you want a simple solution for something complicated and tradeoffy.But to handle that cleanly in more complex cases, you'd really need `final` to be a proper type qualifier, which would mean something like ```d final int i = 42; final(int)* ptr = &i; ``` since without that, it all degrades to const, significantly reducing the utility of final in such code, but making final a type qualifier also opens a very large can of worms which we probably don't want to open.I don't want to open that either, hence making it a storage class. I understand the desire to make it part of the type system, but I want to make it not part of the type system.Again, it will have weird semantics because you want a simple solution for something that’s actually complicated. AFAICT, the yea-sayer see: * A `final int` isn’t complicated, it’s just `const int`. * A `final` reference type isn’t complicated, you can’t modify the reference. * A `final T[]` isn’t too complicated, just don’t allow appending and reassignment. Those can be handled with a template. I don’t know what’s up with `std.experimental.Final`, but it can definitely work for those. I tried implementing it in 2017 and realized: You need specializations for all sorts of types. The nay-sayers see: * All the above are useless. * A `final` struct type with indirections is useless or complicated. What follows is: If the solution mustn’t be complicated, it will be useless. I tried fixing `std.experimental.Final` and realized for head-const, you absolutely have to make it a qualifier and a D template can’t substitute for that. The idea is easy enough: `final` makes the first layer of indirection unmodifiable through this reference. That means you can’t let a `final` struct call mutable member function, but if the struct has indirections, `const` functions ask too much. You need dedicated `final` functions that only protect the first layer of the struct and allow for modification after that. This is the bare minimum for `final` to work with structs and it’s the only place where it needs to be a core-language feature.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.Even then it’s very close to being worthless. I’ve given up on fixing `std.experimental.Final` back then for exactly that reason. It’s not worth it. It can’t work for structs, the only place something like it actually provides value. The simple answer from the nay-sayers is: If you want to code in single assignment style, just do not re-assign. Just don’t do it. For most types, you can even use `const` to tie your hands. And if you really can’t trust yourself or your team members with something that simple, use a linter. After all, it’s syntactically very simple. And I totally get why you like making it a storage class and not a qualifier: It makes the implementation easier, a lot easier. D already has 9 qualifications if you count all distinct combinations of qualifiers, and with `final` those become 13; you’d have to mangle it; it affects overloading (but it requires no new rules, since all relevant rules already exist for `const`); it necessitates a member function attribute to apply it to the implicit `this` parameter; probably more. There’s another reason it’s not a great qualifier: It’s head-`const`. What about head-`immutable`? Head-`shared`? Head-`inout`? I’m with Peter C on one aspect: `final` isn’t a good name.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.
Jan 16
Initial implementation: https://github.com/dlang/dmd/pull/22171
Dec 01 2025
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 2025
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 2025
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 2025
`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 2025
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 2025
You may very well be correct. But my current efforts are to make it work with variables. I've already had to restructure the guts of it more than once. Once it is solid, we can visit what happens with fields.
Dec 04 2025
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 2025
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 2025
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 2025
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 2025
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 2025
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 2025
On 12/3/2025 2:43 AM, Peter C wrote: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?I've made no attempt on that yet.
Dec 04 2025
On Tuesday, 2 December 2025 at 07:50:57 UTC, Walter Bright wrote:Initial implementation: https://github.com/dlang/dmd/pull/22171This should be allowed: final int x; // A final variable declared without an initializer. x = 42; // currently getting -> Error: cannot modify `final x`
Dec 05 2025
On Friday, 5 December 2025 at 22:35:04 UTC, Peter C wrote:On Tuesday, 2 December 2025 at 07:50:57 UTC, Walter BrightThis should be allowed: final int x; // A final variable declared without an initializer. x = 42; // currently getting -> Error: cannot modify `final x`I suggest keeping the topic about D and not a hypothetical language. I in this case `x` is already initialized, so by this DIP, it can not be changed.
Dec 05 2025
On Friday, 5 December 2025 at 23:05:54 UTC, Juraj wrote:On Friday, 5 December 2025 at 22:35:04 UTC, Peter C wrote:I'm pretty sure I *was* talking about D... In any case... single assignment semantics say: you can assign exactly once. where in this declaration, am i assigning? final int x; If the compiler counts default initialization as an assignment, then I've already 'used up' my one assignment before I even touch the variable!On Tuesday, 2 December 2025 at 07:50:57 UTC, Walter BrightThis should be allowed: final int x; // A final variable declared without an initializer. x = 42; // currently getting -> Error: cannot modify `final x`I suggest keeping the topic about D and not a hypothetical language. I in this case `x` is already initialized, so by this DIP, it can not be changed.
Dec 05 2025
On 12/5/2025 3:19 PM, Peter C wrote:If the compiler counts default initialization as an assignment,It does!then I've already 'used up' my one assignment before I even touch the variable!Oh well!
Dec 05 2025
On Saturday, 6 December 2025 at 00:51:37 UTC, Walter Bright wrote:On 12/5/2025 3:19 PM, Peter C wrote:Only assignment uses the assignment operator -> '='! If there is no assignment operator involved, then it cannot reasonably be counted as an assignment! int x; // this is NOT assignment!If the compiler counts default initialization as an assignment,It does!then I've already 'used up' my one assignment before I even touch the variable!Oh well!
Dec 05 2025
On Friday, December 5, 2025 8:37:00 PM Mountain Standard Time Peter C via dip.development wrote:On Saturday, 6 December 2025 at 00:51:37 UTC, Walter Bright wrote:Well, if we want to get technical, talking about "static single assignment" is kind of nonsensical considering what assignment means in most languages - and certainly with what it means in D. The initial value that a variable has comes from initialization or construction, not assignment. Code such as int x = 42; involves no assignment whatsoever. It's initialization, not assignment. Assignment is only used when a variable is given a new value. For instance, T t = 42; would not use T's assignment operator if T overloaded the assignment operator. Rather, it would use T's constructor, because the code is equivalent to T t = T(42); In order to trigger assignment, = would have to be used outside a variable declaration with code such as t = 42; rather than T t = 42; Similarly, assignment is _never_ legal with a const variable, because that would mean mutating the variable, whereas initialization is perfectly legal, because that's giving the variable its initial value. So, what this final proposal is doing is actually saying that a final variable can be initialized but it can never be assigned to and not actually that it can be assigned to only once. The critical difference from const is then that final is head-const rather than full, transitive const. - Jonathan M DavisOn 12/5/2025 3:19 PM, Peter C wrote:Only assignment uses the assignment operator -> '='! If there is no assignment operator involved, then it cannot reasonably be counted as an assignment! int x; // this is NOT assignment!If the compiler counts default initialization as an assignment,It does!then I've already 'used up' my one assignment before I even touch the variable!Oh well!
Dec 05 2025
On Saturday, 6 December 2025 at 07:49:18 UTC, Jonathan M Davis wrote:On Friday, December 5, 2025 8:37:00 PM Mountain Standard Time Peter C via dip.development wrote:Then I stand corrected. int x = 42; // constructs a variable with an initial value - not considered an assignment. x = 100; // performs the first 'assignment' operation. final x = 42; // a final must be initialized with a value at the time of construction. x = 100; // Error In any case, without 'final' (of preferably 'fixed') being a part of the type system, it's just going to involve hack after hack to make it a useful addition to the D language. It'll get ugly. People won't like the hacks. There'll be a call to make it part of the type system itself. That'll be rejected because.. you know.. D is too complex already.. and all that. So.... 100 posts into this already, and nobody seems too enthusiastic about it.. most of us are still confused as to what it can or can't do, or what it will or won't do.... so.. no.. person(s) responsible should provide a better DIP next time, so we don't have to waste all this mental effort that should have been wasted by the person(s) drafting the DIP!On Saturday, 6 December 2025 at 00:51:37 UTC, Walter Bright wrote:Well, if we want to get technical, talking about "static single assignment" is kind of nonsensical considering what assignment means in most languages - and certainly with what it means in D. The initial value that a variable has comes from initialization or construction, not assignment. Code such as int x = 42; involves no assignment whatsoever. It's initialization, not assignment. Assignment is only used when a variable is given a new value. For instance, T t = 42; would not use T's assignment operator if T overloaded the assignment operator. Rather, it would use T's constructor, because the code is equivalent to T t = T(42); In order to trigger assignment, = would have to be used outside a variable declaration with code such as t = 42; rather than T t = 42; Similarly, assignment is _never_ legal with a const variable, because that would mean mutating the variable, whereas initialization is perfectly legal, because that's giving the variable its initial value. So, what this final proposal is doing is actually saying that a final variable can be initialized but it can never be assigned to and not actually that it can be assigned to only once. The critical difference from const is then that final is head-const rather than full, transitive const. - Jonathan M DavisOn 12/5/2025 3:19 PM, Peter C wrote:Only assignment uses the assignment operator -> '='! If there is no assignment operator involved, then it cannot reasonably be counted as an assignment! int x; // this is NOT assignment!If the compiler counts default initialization as an assignment,It does!then I've already 'used up' my one assignment before I even touch the variable!Oh well!
Dec 06 2025
On Friday, 5 December 2025 at 23:05:54 UTC, Juraj wrote:On Friday, 5 December 2025 at 22:35:04 UTC, Peter C wrote:also, if final is ever going to work with class fields, then if default initialization is considered an assignment, then you won't be able to write code like this: class Config { final int port; // declared, default initialized to 0 this(int p) { port = p; // explicit assignment - allowed once. } }On Tuesday, 2 December 2025 at 07:50:57 UTC, Walter BrightThis should be allowed: final int x; // A final variable declared without an initializer. x = 42; // currently getting -> Error: cannot modify `final x`I suggest keeping the topic about D and not a hypothetical language. I in this case `x` is already initialized, so by this DIP, it can not be changed.
Dec 05 2025
On Friday, 5 December 2025 at 23:25:41 UTC, Peter C wrote:
also, if final is ever going to work with class fields, then if
default initialization is considered an assignment, then you
won't be able to write code like this:
class Config
{
final int port; // declared, default initialized to 0
this(int p)
{
port = p; // explicit assignment - allowed once.
}
}
No, read https://dlang.org/spec/class.html#field-init. That's
initialization.
Dec 06 2025
On Saturday, 6 December 2025 at 11:45:21 UTC, Nick Treleaven wrote:On Friday, 5 December 2025 at 23:25:41 UTC, Peter C wrote:So, 'technically' I stand corrected again. But regardless of this semantic distinction, the first instance of the **assignment** operator (=) to a final variable is the one that permanently **assigns** its value. The mental model of 'assignment is the operation performed by the assignment operator', is conceptually cleaner to me. There is absolutely no need for my brain to decide whether I'm actually initializing or assigning; it just adds no value, in this context, to the practical task of writing correct code.also, if final is ever going to work with class fields, then if default initialization is considered an assignment, then you won't be able to write code like this: class Config { final int port; // declared, default initialized to 0 this(int p) { port = p; // explicit assignment - allowed once. } }No, read https://dlang.org/spec/class.html#field-init. That's initialization.
Dec 06 2025
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.htmlif its not going to be part of the type system it shouldnt exist; your pandering to safetyphiles with a tool they wont use, like live, so you can talk about it on hakernews, like live. Hakernews driven development may not be sound design process. I suggest asking for a template wizard to handle whatever your compiler dev usecase with a uda hack. Everyone else uses smaller functions with less goto's when they get confused.
Dec 05 2025
On Friday, 5 December 2025 at 12:20:29 UTC, monkyyy wrote:On Saturday, 15 November 2025 at 07:13:13 UTC, Walter Bright wrote:Not every safety feature has to be a deep type-system construct. Some are useful simply as pragmatic compiler-enforced checks that improve clarity and reduce bugs. If final can ensure a variable is assigned only once - helping prevent subtle reassignment bugs without complex changes to the type machinery - then it's worth investigating. And that, as I understand it, is exactly what's happening here. I'd also note that dismissive, personally insulting, and confrontational comments don't add much to the discussion. It would be more helpful to focus on building a clear technical critique that moves the conversation forward.https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.htmlif its not going to be part of the type system it shouldnt exist; your pandering to safetyphiles with a tool they wont use, like live, so you can talk about it on hakernews, like live. Hakernews driven development may not be sound design process. I suggest asking for a template wizard to handle whatever your compiler dev usecase with a uda hack. Everyone else uses smaller functions with less goto's when they get confused.
Dec 05 2025
On Friday, 5 December 2025 at 22:50:49 UTC, Peter C wrote:On Friday, 5 December 2025 at 12:20:29 UTC, monkyyy wrote:It's impossible to focus when you don't say what you believe with your own words. Monkeyyyyy has a point. If a language feature is just used for one tiny specific case, then why add it in the first place? I might change my mind later if I read every message in this forum post. For `final` to be useful and worth the change, it should work like head const in my opinion and be usable inside structs, classes and function arguments.On Saturday, 15 November 2025 at 07:13:13 UTC, Walter Bright wrote:Not every safety feature has to be a deep type-system construct. Some are useful simply as pragmatic compiler-enforced checks that improve clarity and reduce bugs. If final can ensure a variable is assigned only once - helping prevent subtle reassignment bugs without complex changes to the type machinery - then it's worth investigating. And that, as I understand it, is exactly what's happening here. I'd also note that dismissive, personally insulting, and confrontational comments don't add much to the discussion. It would be more helpful to focus on building a clear technical critique that moves the conversation forward.https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.htmlif its not going to be part of the type system it shouldnt exist; your pandering to safetyphiles with a tool they wont use, like live, so you can talk about it on hakernews, like live. Hakernews driven development may not be sound design process. I suggest asking for a template wizard to handle whatever your compiler dev usecase with a uda hack. Everyone else uses smaller functions with less goto's when they get confused.
Dec 06 2025
On Friday, 5 December 2025 at 12:20:29 UTC, monkyyy wrote:I suggest asking for a template wizard to handle whatever your compiler dev usecase with a uda hack.I don't think that's possible - making a struct Final which acts like a type constructor. E.g. you can only have one `alias this`, and it needs to rvalue convert to mutable and lvalue convert to const.
Dec 06 2025
On Saturday, 6 December 2025 at 12:00:44 UTC, Nick Treleaven wrote:On Friday, 5 December 2025 at 12:20:29 UTC, monkyyy wrote:Does the compilers build chain break my favorite compiler bugs? If I have all my tools its possible, nothing that be merged tho. Pretty trivial to make it caught at runtime as well.I suggest asking for a template wizard to handle whatever your compiler dev usecase with a uda hack.I don't think that's possible - making a struct Final which acts like a type constructor. E.g. you can only have one `alias this`, and it needs to rvalue convert to mutable and lvalue convert to const.
Dec 06 2025
On Saturday, 6 December 2025 at 12:00:44 UTC, Nick Treleaven wrote:On Friday, 5 December 2025 at 12:20:29 UTC, monkyyy wrote:About the UDA - that would require some mechanism to enforce it. I don't think the language can do that, particularly not with local variables.I suggest asking for a template wizard to handle whatever your compiler dev usecase with a uda hack.I don't think that's possible- making a struct Final which acts like a type constructor. E.g. you can only have one `alias this`, and it needs to rvalue convert to mutable and lvalue convert to const.Today I made a `Final` struct template. It's incomplete. What is there, I found issues with, and there are probably more. Initial ones: 1. const lvalue conversion is not implicit. When a head-const value is needed outside `Final!T`, it just makes an rvalue. Which is OK for certain types of T, but obviously larger values will do too much copying. 2. The operator overloads have to be conservative, e.g. opUnary returns an rvalue for structs because how can it tell at compile-time if a `ref T.opUnary` is referring to part of T's memory? https://github.com/ntrel/stuff/blob/master/final.d Perhaps someone else can do better, but this design doesn't seem robust enough for general use, even if it was finished.
Dec 10 2025
On Wednesday, 10 December 2025 at 21:50:14 UTC, Nick Treleaven wrote:On Saturday, 6 December 2025 at 12:00:44 UTC, Nick Treleaven wrote:static assert exists, given any `isFoo` template, it can be forced into an `assertFoo`On Friday, 5 December 2025 at 12:20:29 UTC, monkyyy wrote:About the UDA - that would require some mechanism to enforce it. I don't think the language can do that, particularly not with local variables.I suggest asking for a template wizard to handle whatever your compiler dev usecase with a uda hack.I don't think that's possible- making a struct Final which acts like a type constructor. E.g. you can only have one `alias this`, and it needs to rvalue convert to mutable and lvalue convert to const.Today I made a `Final` struct template. It's incomplete. What is there, I found issues with, and there are probably more. Initial ones: 1. const lvalue conversion is not implicit. When a head-const value is needed outside `Final!T`, it just makes an rvalue. Which is OK for certain types of T, but obviously larger values will do too much copying. 2. The operator overloads have to be conservative, e.g. opUnary returns an rvalue for structs because how can it tell at compile-time if a `ref T.opUnary` is referring to part of T's memory? https://github.com/ntrel/stuff/blob/master/final.dVERY much disagree, youd break any code that was a wrapper of a pointer// Bug: doesn't prevent assigning to a struct result which has opAssign defined // https://github.com/dlang/dmd/issues/21507Perhaps someone else can do better, but this design doesn't seem robust enough for general use, even if it was finished.??? why have these do different behavior.. whatever ```d import std; T recast(T,S)(ref S s){ return *cast(T*)(&s); } template classifydata(T){ static if( is(T == U[], U)){ enum classifydata=1; } else { static if( is(T == U[N], U,size_t N)){ enum classifydata=2; } else { static if( is(T == U[A], U,A)){ enum classifydata=3; } else { enum classifydata=0; }}} } unittest{ static assert(classifydata!(int)==0); static assert(classifydata!(int[])==1); static assert(classifydata!(int[3])==2); static assert(classifydata!(int[int])==3); } struct Final(T){ T data; enum whatthis=classifydata!T; this(T t){ data=t; } static if(whatthis==0){ T get()=> data; alias get this; } static if(whatthis==1){ void opOpAssign(string op:"~",S)(S a){ data~=a; }} static if(whatthis>0){//whatthis==1||whatthis==2){ auto opIndex(I)(I i){ alias S=typeof(T.init[0]); return data[i].recast!(Final!S); }} static if(whatthis==3){ auto opIndexAssign(S,I)(S s,I i){ assert( ! (i in data));//runtime is probaly the best for aa's data[i]=s; }} } unittest{ auto foo=Final!int(3); foo.writeln; auto bar=Final!(int[])([1,2,3]); bar~=4; bar.writeln; //bar[2]=3; bar[2].writeln; } unittest{ Final!(int[string]) foo; foo["hi"]=3; foo["bye"]=5; foo.writeln; //foo["hi"]=7; } unittest{ Final!(int[][3]) foo; foo[0]~=1; foo[1]~=[2,3]; foo.writeln; //foo[0][0]=7; } ``` youd have to track down the datastructures the compiler uses to update "classify data" but this is the direction id suggest tryingref opIndex()(size_t i) if (is(T == U[], U)) => v[i]; auto opIndex()(size_t i) if (is(T == U[n], U, size_t n)) => v[i];
Jan 13
I've been having a bit of trouble putting into words what I want to say here following more information gathered in this thread. This is not a solution to the head const problems that made me want it in the past nor does it align with what I remember other people have had either. My earlier excitement to this has now changed to: "why would we ever want something like this?". It is for all intents and purposes a worse const which is trying to solve a problem that does not exist. The problem I and others have, is wanting interior mutability, with external immutability. It is not a unique problem to D, other languages like Rust have a solution to it (keep in mind mutability is opt-in), via the Cell struct. https://doc.rust-lang.org/std/cell/struct.Cell.html A solution you can do in Swift similarly: https://mcky.dev/blog/interior-mutability-swift/ What is desired is the opposite of Rebindable: https://dlang.org/phobos/std_typecons.html#Rebindable So that: 1. A pointer may not be changed once set 2. The contents of an object pointed to from a pointer may be changed 3. Taking a pointer to that pointer won't compile or will be const(T*) On the other hand this proposal is for all intents and purposes const except: You can copy it and it won't be const anymore. As long as this does more than protect an exterior pointer, methods will need to have their this pointer marked as final or const to be callable.
Dec 05 2025
On Saturday, 6 December 2025 at 04:26:53 UTC, Richard (Rikki) Andrew Cattermole wrote:..The real problem is: the proposal for a 'storage-class' final is just a band-aid for local rebinding bugs. That is, it applies only to the specific binding in the scope where it's declared. That in itself can be very useful. But it doesn't deliver any systemic guarantees, because it's just lexical, not semantic. Once you copy or pass that reference around, the guarantee simply evaporates. Then you have to introduce this hack and that hack...and it gets messy pretty quickly. Layering on hacks is not the solution. For final to be useful beyond the scope in which it is declared, the guarantee must live in the type system, not just at the declaration site. I propose: (1) Adopt 'fixed' as a *contextual* keyword for single-assignment semantics. (2) Make 'fixed' semantic, not just lexical: encode non-rebindability in the type itself, so the guarantee persists across copies and APIs. Benefits: - Single-assignment binding: The variable is initialized once; rebinding is disallowed. - Reference identity guarantee: The handle's target identity remains stable; the object's internal state is orthogonal. - No decay on copy (if type-level): If 'fixed' is only a storage class, copies lose the guarantee. If 'fixed' is part of the type, the guarantee survives copies and API boundaries. - Head-const behavior (optional design): Taking pointers or references from a 'fixed' binding yields a head-const view (e.g., const(T*)), preventing mutable pointer-to-pointer leaks. -- this is really how I want it to work -- public class Database { void bump() {} } void main() { fixed Database db = new Database(); db = new Database(); // Error: cannot modify `fixed db` auto copy = db; // fine. And copy is itself 'fixed'. copy = new Database(); // Error: cannot modify `fixed copy` db.bump(); // interior mutation still allowed }
Dec 05 2025
On Saturday, 6 December 2025 at 06:41:19 UTC, Peter C wrote:... -- this is really how I want it to work --What a true type‑level fixed annotation would look like if it were added to D: // Passing fixed into functions class Database { void bump() {} } void useDb(fixed Database db) { db.bump(); // interior mutation allowed db = new Database(); // error: cannot rebind fixed parameter } void main() { fixed Database db = new Database(); useDb(db); // passes as fixed, guarantee preserved. } // ----------------------------------- // Returning fixed from functions fixed Database makeDb() { return new Database(); // initialized once, returned as fixed } void main() { auto db = makeDb(); // inferred as fixed Database db = new Database(); // error: cannot rebind db.bump(); // interior mutation allowed } // ---------------------------
Dec 05 2025
On 11/14/2025 11:13 PM, Walter Bright wrote:https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.htmlMy implementation isn't going too well. I keep finding more and more special cases, and some that are difficult to resolve. The problems stem from the complex ways one can do an assignment. Ironically, this complexity is dealt with via the type system. But final isn't a part of the type system, so things become a morass of special cases. I thought this would be a simple implementation :-/ It's hard to see if the benefits of final outweigh the complications. What do you think? The current state of affairs: https://github.com/dlang/dmd/pull/22171
Dec 06 2025
On Sunday, 7 December 2025 at 06:02:53 UTC, Walter Bright wrote:Ironically, this complexity is dealt with via the type system. But final isn't a part of the type system, so things become a morass of special cases.I suggest maybe this should fail: ```d safe: import std; unittest { const int foo = void; foo.writeln;//error safe const was never initialized } ``` and then "final assignments" to const are allowed if a value is marked as void ```d unittest{ const int foo=void; goto bar; notreal: final foo=3;//no op bar: final foo=5;//allowed cans number flow whatevers sees foo as void foo.writeln; }
Dec 07 2025
On Sunday, 7 December 2025 at 06:02:53 UTC, Walter Bright wrote:On 11/14/2025 11:13 PM, Walter Bright wrote:I guess all I can say is, I tried to warn you.https://www.digitalmars.com/d/archives/digitalmars/dip/ideas/Single_Assignment_1765.htmlMy implementation isn't going too well. I keep finding more and more special cases, and some that are difficult to resolve. The problems stem from the complex ways one can do an assignment. Ironically, this complexity is dealt with via the type system. But final isn't a part of the type system, so things become a morass of special cases. I thought this would be a simple implementation :-/
Dec 07 2025









Walter Bright <newshound2 digitalmars.com> 