digitalmars.dip.ideas - Single Assignment
- Walter Bright (37/37) Nov 14 Single assignment is the idea that a variable can only be assigned to on...
- Richard (Rikki) Andrew Cattermole (26/26) Nov 14 Originally I was going to ask the question if its just an alias to
- Walter Bright (2/2) Nov 14 Headconst as a storage class for the variable doesn't really work, becau...
- Richard (Rikki) Andrew Cattermole (15/17) Nov 14 Hang on:
- Walter Bright (3/27) Nov 14 or allowed at all.
- monkyyy (16/17) Nov 14 My ct mutability features are often forced to be; it doesnt
- Dmitry Olshansky (5/41) Nov 14 final seems like a good candidate for this. Considering it’s a
- Walter Bright (7/10) Nov 14 Consider:
- Walter Bright (1/1) Nov 14 Yes, final is a good idea.
- Jonathan M Davis (56/60) Nov 14 I use it fairly frequently, but at the end of the day, I just do what se...
- Walter Bright (3/3) Nov 14 On a short function, I agree that any compiler enforcement of single ass...
- Peter C (9/13) Nov 15 In Java, final is a multipurpose keyword, overloaded with
- Peter C (45/53) Nov 15 Well, if had to choose better the clarity of a wrapper class vs
- Lance Bachmeier (10/15) Nov 14 No. I rarely use const because dealing with transitivity is far
- jmh530 (6/22) Nov 14 It seems like your concern is more about making it easier for
- Walter Bright (5/9) Nov 14 There is an issue with whether single assignment declaration should be t...
- ltdk (8/19) Nov 14 I prefers this over Rust approach, it makes development much
- Dukc (22/27) Nov 14 Mostly yes. If a variable is logically a variable (like some sort
- Walter Bright (3/12) Nov 14 `alias` is a distinct feature from single assignment.
- Meta (20/26) Nov 14 1. Always, unless there's a good reason not to.
- Paul Backus (10/21) Nov 14 I assume this also means that non-const struct and class methods
- Meta (4/27) Nov 14 Sure, that's why I say the cost is low, but the benefit is also
- user1234 (5/7) Nov 27 So D doesn't like warnings, big tradition. Usually D doesnt force
- Peter C (6/13) Nov 27 When 'final' (or 'fixed' as I would prefer it to be called)
- Walter Bright (1/1) Nov 14 You make a good point. I think you're right.
- Walter Bright (7/8) Nov 14 One saving grace is you can write:
- Walter Bright (1/1) Nov 14 I posted a draft DIP over in d.dip.development
- BobbyFischer (5/6) Nov 15 Could you please post the URL so I can read?
- Peter C (28/31) Nov 15 Just introduce a new non-transitive, single-assignment keyword
- monkyyy (2/4) Nov 15 .... no thats one of my common function names
- Peter C (56/58) Nov 15 No. I don't think so.
- Clouudy (4/16) Nov 16 If I understand this right, the idea is to make some variables
- Peter C (21/25) Nov 16 No, 'init' or 'final' or whatever it is called, on its own, is
- Clouudy (4/24) Nov 27 I believe that the end result is still less flexibility than
- Atila Neves (5/10) Nov 17 Nearly always, but sometimes the code is better if there's
- Quirin Schroll (38/47) Nov 18 I don’t use D professionally, unfortunately, but C++. I’ve
- Salih Dincer (12/27) Nov 22 As you know, in Rust, variables are immutable by default. To
- Kagamin (12/14) Nov 24 Yes, most variables are integers or strings, I just mark them as
- Peter C (28/29) Nov 24 class Data { int value = 1; }
Single assignment is the idea that a variable can only be assigned to once. This idea has been around for a while as recommended style, as in: Bad style: ```d int a = 3; a = 4; ``` Good style: ```d int a = 3; int b = 4; ``` It's good style because it improves the comprehensibility of the code. The bad style was good when compiler did not do register allocation by live range analysis, so re-using variables was a good idea. But compilers are pretty good at that now, so that is no longer an issue. Single assignment has become popular in other languages. In D, we can approach this with `const`: ```d const int a = 3; a = 4; // error: a is const ``` But there is a problem: ```d int a = 3; const int* p = &a; *p = 4; // error a = 4; // ok ``` This is because `const` is transitive. Allowing things like a "const pointer to mutable" would be quite disruptive to the D type system, and may wind up being excessively complex. So, some food for thought: 1. do you use single assignment in D? (I try to use it.) 2. is const, with its transitivity, good enough? 3. if not good enough, is there a practical way for D to enforce it? 4. is this a waste of time chasing rainbows?
Nov 14
Originally I was going to ask the question if its just an alias to
another variable why would you need another variable?
But then I remembered ternary operator.
```d
int val = gate ? arg1 : anotherVar;
```
Essentially what you have proposed is headconst that is a storage class
not a type qualifier.
Thinking along the lines of by-ref storage classes, we could write it
like so:
```d
int* ptr = new int;
func(ptr);
headconst val = new int;
func(val);
void func(headconst int val) {
}
```
Basic types don't need this, they'll cast off const quite happily.
Lazy also needs a similar treatment so it can only be passed around if
the variable is marked as lazy.
I don't have answers as to if it would be a good idea to add, but I
would use it.
On another related note, I want the effect effectivelyconst modeled to
help with a borrow checker. I don't think it can be incorporated into
this just related.
Nov 14
Headconst as a storage class for the variable doesn't really work, because a pointer to the variable can change it.
Nov 14
On 15/11/2025 7:21 AM, Walter Bright wrote:Headconst as a storage class for the variable doesn't really work, because a pointer to the variable can change it.Hang on: ```d int* ptr0 = new int; headconst int ptr1 = ptr0; auto ptr2 = &ptr1; assert(ptr2 is ptr0); ``` How do you get a pointer to the storage of the pointer? Why would the following work? ```d headconst int ptr1 = ...; int** ptr3 = &&ptr1; ``` I'm not sure taking a pointer to that var should be allowed in safe code.
Nov 14
On 11/14/2025 10:26 AM, Richard (Rikki) Andrew Cattermole wrote:On 15/11/2025 7:21 AM, Walter Bright wrote:Take the address of a pointer.Headconst as a storage class for the variable doesn't really work, because a pointer to the variable can change it.Hang on: ```d int* ptr0 = new int; headconst int ptr1 = ptr0; auto ptr2 = &ptr1; assert(ptr2 is ptr0); ``` How do you get a pointer to the storage of the pointer?Why would the following work? ```d headconst int ptr1 = ...; int** ptr3 = &&ptr1; ``` I'm not sure taking a pointer to that var should be allowed in safe code.or allowed at all.
Nov 14
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:1. do you use single assignment in D? (I try to use it.)My ct mutability features are often forced to be; it doesnt belong in runtime code. If its possible to compute something in one call, it shouldn't ever even exist. One could imagine a iterator api in oo where `empty` *had to* be a bool and not an bool function, and it becoming a problem if code with a ref to a range read that empty held onto the reference and then it changed under it (such as me appending to it). In template hell, empty is the users function and it gets inlined where its needed, in oo I bet it wouldve eventually been a "problematic source of bugs" that "we all need to enforce invarents of" and if there was something that slipped thru and needed backwards compatibility such oo programmers would try to make "atomic reads" of this empty and drastically expanding the numbers of reads, thinking they are the best at following a "single source of truth principal".
Nov 14
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:Single assignment is the idea that a variable can only be assigned to once. This idea has been around for a while as recommended style, as in: Bad style: ```d int a = 3; a = 4; ``` Good style: ```d int a = 3; int b = 4; ``` It's good style because it improves the comprehensibility of the code. The bad style was good when compiler did not do register allocation by live range analysis, so re-using variables was a good idea. But compilers are pretty good at that now, so that is no longer an issue. Single assignment has become popular in other languages. In D, we can approach this with `const`: ```d const int a = 3; a = 4; // error: a is const ``` But there is a problem: ```d int a = 3; const int* p = &a; *p = 4; // error a = 4; // ok ``` This is because `const` is transitive. Allowing things like a "const pointer to mutable" would be quite disruptive to the D type system, and may wind up being excessively complex.final seems like a good candidate for this. Considering it’s a storage class it may not disrupt the type system all that much. Java’s final is basically this. Scala/Kotlin val is implicitly final.
Nov 14
On 11/14/2025 3:25 AM, Dmitry Olshansky wrote:final seems like a good candidate for this. Considering it’s a storage class it may not disrupt the type system all that much. Java’s final is basically this. Scala/Kotlin val is implicitly final.Consider: ```d final int a = 3; int* p = &a; *p = 4; // allowed or not? ```
Nov 14
On Friday, November 14, 2025 1:19:28 AM Mountain Standard Time Walter Bright via dip.ideas wrote:1. do you use single assignment in D? (I try to use it.)I use it fairly frequently, but at the end of the day, I just do what seems to make the most sense for the code. Typically, I'll reuse a variable when the nature of the variable is such that reuse makes sense, but I don't typically use the same variable for different things. But I don't typically use const for any of this. I just don't reassign the variable, and if you're dealing with more complex objects, the variable is usually going to be mutated even if you don't assign it a new value - e.g. I typically won't reassign a variable that's a range, but iterating it mutates it. So, if by single assignment, the expectation is a lack mutability (be it for the object sitting on the stack or what it refers to), then I very rarely use it. But if the idea is just to use separate variables for separate things rather than reusing an existing variable, then I do that frequently.2. is const, with its transitivity, good enough?If you're trying to enforce head-const, then const really doesn't work because of transitivity.3. if not good enough, is there a practical way for D to enforce it?If the goal is to avoid having the variable be reassigned but not necessarily care about the general mutability of the object, then it can be enforced with a wrapper struct which disallows assignment while forwarding all of the other calls to the object itself, though that does get in the way of passing the variable to other functions. We could theoretically add a new storage class which dealt with it - presumably one which disallowed assignment but allowed you to call member functions, and you could pass it by value (since that would copy) but not by ref unless that ref were const or inout (since otherwise, it could be assigned to). But with the language as it stands, the best way way to do it is to use a wrapper structs, which some folks have done before.4. is this a waste of time chasing rainbows?I'm inclined to think so. Honestly, if it weren't for the fact that immutable is implicitly shared and the fact that that can be extremely useful, I'd be tempted to argue that both const and immutable are already a waste of time in general, because they're just too restrictive once you're dealing with actual objects rather than simple examples with primitive types. So, adding even more types of constness seems like a move in the wrong direction to me. It might not be as bad if it's just a storage class and not a type qualifier, but it's still more complexity for something where I don't see much value in having the compiler enforce anything. If you don't want to reassign a variable, you can just choose to not reassign it - and unless your function is hugely long, it's not like that would be hard to keep track of. Maybe there would be some value in being able to enforce it programmatically without needing a wrapper type, but it would also complicate the language further, and personally, I think that the benefit is minimal enough to not merit the additional complexity. Also, I'm not sure what practical benefit there really is to enforcing single assignment anyway. It can make it easier to understand the code, but why would the compiler need to try to enforce any kind of understandability? Especially when it's trivial for the programmer to get it right if that's what they want. It sounds closer to enforcing formatting rules or trying to require informative variable names than to doing anything practical with regards to compiling code. If anything, I think that the major hole with regards to type qualifiers in D is the inability to have tail-modifiers with class references without some kind of wrapper struct which does casting to make it happen. The workarounds for the lack of tail-const, tail-immutable, or tail-shared with classes are ugly and difficult to get right (and pretty questionable with regards to type correctness). - Jonathan M Davis
Nov 14
On a short function, I agree that any compiler enforcement of single assignment is pointless. But for a longer one, it's a good documentation aid. I like your idea of using `final` to trigger it. Thanks!
Nov 14
On Friday, 14 November 2025 at 18:47:43 UTC, Walter Bright wrote:On a short function, I agree that any compiler enforcement of single assignment is pointless. But for a longer one, it's a good documentation aid. I like your idea of using `final` to trigger it. Thanks!In Java, final is a multipurpose keyword, overloaded with multiple meanings! Please don't do this in D. D has enough problems already ;-) Instead, see my previous post re: the proposal for 'init' - a new keyword which has a single lase focuced purpose only - and carries none of the baggage that would come with final. 'init' -> you initialize it once, and you're done. 'init' is purpose built ..for D.
Nov 15
On Friday, 14 November 2025 at 13:13:38 UTC, Jonathan M Davis wrote:... If the goal is to avoid having the variable be reassigned but not necessarily care about the general mutability of the object, then it can be enforced with a wrapper struct which disallows assignment while forwarding all of the other calls to the object itself, though that does get in the way of passing the variable to other functions. ...Well, if had to choose better the clarity of a wrapper class vs the clarity of init, for enforcing single-assignment, I would choose init always. In this example below, the logic of using 'init' (in addition to the clarity it provides) is sound: init provides the intended safety invariant without sacrificing the necessary mutability required for D range consumption. module mymodule; safe: private: import std.algorithm : filter; class DataProcessor(R) { static bool isEven(int a) { return a % 2 == 0; } static auto getFilterType(R source) { return source.filter!isEven; } init typeof(getFilterType(R.init)) filteredView; this(R source) { this.filteredView = source.filter!isEven; } int getNextEvenNumber() { if (filteredView.empty) { return -1; } int value = filteredView.front; filteredView.popFront(); return value; } void resetToNewData(R newData) { this.filteredView = newData.filter!isEven; // Error: Fields declared with init can only be assigned once. } }
Nov 15
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:1. do you use single assignment in D? (I try to use it.)This is my first choice.2. is const, with its transitivity, good enough?No. I rarely use const because dealing with transitivity is far worse than any benefit from using const.3. if not good enough, is there a practical way for D to enforce it?As Dmitry said, Scala's val does this, and I've always wished we had it. val also prohibits the use of default values, so `val x: Int;` is not allowed. You have to specify `val x: Int = 6;` or `val x = 6;`.4. is this a waste of time chasing rainbows?No. It would be one of those additions that make the language more fun to use.
Nov 14
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:Single assignment is the idea that a variable can only be assigned to once. This idea has been around for a while as recommended style, as in: Bad style: ```d int a = 3; a = 4; ``` Good style: ```d int a = 3; int b = 4; ``` It's good style because it improves the comprehensibility of the code. [snip]It seems like your concern is more about making it easier for people to write code in a single assignment way, rather than preventing people from using the "bad style" way. I don't have an issue with that. Sometimes the "bad style" is good enough for a quick and dirty script.
Nov 14
On 11/14/2025 9:34 AM, jmh530 wrote:It seems like your concern is more about making it easier for people to write code in a single assignment way, rather than preventing people from using the "bad style" way. I don't have an issue with that. Sometimes the "bad style" is good enough for a quick and dirty script.There is an issue with whether single assignment declaration should be the default, and a "mutable" declaration should require a keyword. Unfortunately, there is so much water under that bridge that defaulting to single assignment would be overly disruptive. I doubt even editions could save it!
Nov 14
On Friday, 14 November 2025 at 20:51:06 UTC, Walter Bright wrote:On 11/14/2025 9:34 AM, jmh530 wrote:I prefers this over Rust approach, it makes development much faster (compared to Rust). Whenever I need to create something, I will create a quick, dirty implementation first ( don't let me fight the compiler at this state, it's wasteful and distract developers). When time comes, the POC/demo proves to be useful and worth further development, i start to think about those safety aspects.It seems like your concern is more about making it easier for people to write code in a single assignment way, rather than preventing people from using the "bad style" way. I don't have an issue with that. Sometimes the "bad style" is good enough for a quick and dirty script.There is an issue with whether single assignment declaration should be the default, and a "mutable" declaration should require a keyword. Unfortunately, there is so much water under that bridge that defaulting to single assignment would be overly disruptive. I doubt even editions could save it!
Nov 14
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:1. do you use single assignment in D? (I try to use it.)Mostly yes. If a variable is logically a variable (like some sort of a counter for a loop) then I'll re-assign it. But for intermediate values for calculations, singe assignment only, usually.2. is const, with its transitivity, good enough?Not as a general purpose tool. You could use it when you don't have indirections, or when you aren't going to mutate the pointed data anyway, but this isn't nearly always. Personally, I use `immutable`/`const` only when the pointed-to data is read-only.3. if not good enough, is there a practical way for D to enforce it?Limiting the lifetime of variable for when it's actually supposed to be used sometimes makes sense. I sometimes even write `if(true){ /*...*/ }` blocks for that. I know that a block statement without `if(true)` would work just as well, but I feel that the blocks are easier to distinguish visually from the surrounding code when they have a header.4. is this a waste of time chasing rainbows?Not necessarily for linting tools, but I don't think it's worth to add a language-level feature for. However, improving local functions and/or `alias` so they can be used in place of single assignment variables more often, or language level tuples with pattern matching, so you can do `auto a, b = {/* define and return a and b... */}();` could be worth it.
Nov 14
On 11/14/2025 9:37 AM, Dukc wrote:Limiting the lifetime of variable for when it's actually supposed to be used sometimes makes sense. I sometimes even write `if(true){ /*...*/ }` blocks for that. I know that a block statement without `if(true)` would work just as well, but I feel that the blocks are easier to distinguish visually from the surrounding code when they have a header.Hmm, interesting stylistic choice. I never thought of that!However, improving local functions and/or `alias` so they can be used in place of single assignment variables more often, or language level tuples with pattern matching, so you can do `auto a, b = {/* define and return a and b... */}();` could be worth it.`alias` is a distinct feature from single assignment.
Nov 14
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:So, some food for thought: 1. do you use single assignment in D? (I try to use it.) 2. is const, with its transitivity, good enough? 3. if not good enough, is there a practical way for D to enforce it? 4. is this a waste of time chasing rainbows?1. Always, unless there's a good reason not to. 2. Nope. 3. Ideally the default would be flipped a la Rust or Swift, where head const is the default, and variables must be marked as mutable to be mutated. 4. Final as a storage class like Dmitry suggested would probably be the easiest and fastest way to do it, so the cost would be low, but the benefit would also be very low. As for whether a pointer to a "final" value should be able to modify the value it points to, I'd say yes. We already have transitive const and immutable, so the programmer can choose their level of strictness: final, which just prevents reassignment and is not transitive, const, which prevents any modification whatsoever, but does not guarantee that the value won't be modified by other threads, and immutable, which is transitive, and prevents modification by *any* thread. However, having to write final everywhere would be really annoying. I rarely use it in Java and just trust myself to be disciplined and adhere to the single assignment principle.
Nov 14
On Friday, 14 November 2025 at 18:53:05 UTC, Meta wrote:4. Final as a storage class like Dmitry suggested would probably be the easiest and fastest way to do it, so the cost would be low, but the benefit would also be very low. As for whether a pointer to a "final" value should be able to modify the value it points to, I'd say yes. We already have transitive const and immutable, so the programmer can choose their level of strictness: final, which just prevents reassignment and is not transitive, const, which prevents any modification whatsoever, but does not guarantee that the value won't be modified by other threads, and immutable, which is transitive, and prevents modification by *any* thread.I assume this also means that non-const struct and class methods called on a final variable are allowed to mutate it, via the 'this' reference. Frankly, this seems like such a weak guarantee that I cannot imagine it being of any practical use. When you see a reference to a const variable, you can always find out what its value is by looking back at its declaration, without reading any of the code in between. With final, you would still have to read all of that intervening code to see if there are any indirect mutations.
Nov 14
On Friday, 14 November 2025 at 20:55:30 UTC, Paul Backus wrote:On Friday, 14 November 2025 at 18:53:05 UTC, Meta wrote:Yes.4. Final as a storage class like Dmitry suggested would probably be the easiest and fastest way to do it, so the cost would be low, but the benefit would also be very low. As for whether a pointer to a "final" value should be able to modify the value it points to, I'd say yes. We already have transitive const and immutable, so the programmer can choose their level of strictness: final, which just prevents reassignment and is not transitive, const, which prevents any modification whatsoever, but does not guarantee that the value won't be modified by other threads, and immutable, which is transitive, and prevents modification by *any* thread.I assume this also means that non-const struct and class methods called on a final variable are allowed to mutate it, via the 'this' reference.Frankly, this seems like such a weak guarantee that I cannot imagine it being of any practical use. When you see a reference to a const variable, you can always find out what its value is by looking back at its declaration, without reading any of the code in between. With final, you would still have to read all of that intervening code to see if there are any indirect mutations.Sure, that's why I say the cost is low, but the benefit is also low. It's just a compiler-enforced lint.
Nov 14
On Saturday, 15 November 2025 at 01:37:36 UTC, Meta wrote:Sure, that's why I say the cost is low, but the benefit is also low. It's just a compiler-enforced lint.So D doesn't like warnings, big tradition. Usually D doesnt force you to do something. The Idea was that either it's good or an error. It's indeed (single assignement) something a linter could check.
Nov 27
On Friday, 28 November 2025 at 00:15:30 UTC, user1234 wrote:On Saturday, 15 November 2025 at 01:37:36 UTC, Meta wrote:When 'final' (or 'fixed' as I would prefer it to be called) appears, it documents intent and becomes part of the public contract. As such, it 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.Sure, that's why I say the cost is low, but the benefit is also low. It's just a compiler-enforced lint.So D doesn't like warnings, big tradition. Usually D doesnt force you to do something. The Idea was that either it's good or an error. It's indeed (single assignement) something a linter could check.
Nov 27
You make a good point. I think you're right.
Nov 14
Good points! On 11/14/2025 10:53 AM, Meta wrote:However, having to write final everywhere would be really annoying.One saving grace is you can write: ``` final abc = 3; ``` as `abc` is inferred to be `int`.
Nov 14
I posted a draft DIP over in d.dip.development
Nov 14
On Saturday, 15 November 2025 at 07:14:24 UTC, Walter Bright wrote:I posted a draft DIP over in d.dip.developmentCould you please post the URL so I can read? -- The truth: https://vimeo.com/1135589329
Nov 15
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:Single assignment is the idea that a variable can only be assigned to once. ...Just introduce a new non-transitive, single-assignment keyword for local variables in D. We'll call this new keyword: 'init' The init Principle is simple: Once and Done! The init solution provides the local binding safety while keeping the data mutable, avoiding the rigidity imposed by the transitive nature of D's const. 'init' can apply to local variables within a function, struct, class, or interface. The goal is to enforce the single assignment rule on the binding name itself across these structural scopes. module mymodule; // safe: private: //import std; void main() { init int a = 3; // Once and Done! // The init keyword is not transitive. // It does not change the underlying type of the data. // Although it can only be assigned to once, the data can still be mutated. int* p = &a; // fine *p = 4; // fine a = 5; // ERROR: Cannot assign to 'a' because it is an init binding. }
Nov 15
On Saturday, 15 November 2025 at 08:39:36 UTC, Peter C wrote:We'll call this new keyword: 'init'.... no thats one of my common function names
Nov 15
On Saturday, 15 November 2025 at 16:16:01 UTC, monkyyy wrote:On Saturday, 15 November 2025 at 08:39:36 UTC, Peter C wrote:The D compiler already has specific category of tokens that can appear before a type in a declaration - aka 'type qualifiers'. E.g. const and immutable. 'init' would be added to that category. Then, 'init' would have a specific, unambiguous meaning when it precedes a type in a declaration, allowing the parser (and humans for that matter) to distinguish it from a regular identifier used elsewhere. In the examples below, the parser would use the new grammer rule to determine its meaning: - Local variable void main() { init int x = 42; x = 99; // ERROR } - Struct field struct Config { init string name; int version; this(string n, int v) { name = n; // allowed once version = v; } } - Class field class Device { init string serialNumber; this(string sn) { serialNumber = sn; // allowed once } } - Function parameter void process(init string id) { // id cannot be rebound inside function } - Global/module variable module settings; init Config globalConfig = Config("default");We'll call this new keyword: 'init'.... no thats one of my common function names
Nov 15
On Saturday, 15 November 2025 at 23:55:47 UTC, Peter C wrote:Then, 'init' would have a specific, unambiguous meaning when it precedes a type in a declaration, allowing the parser (and humans for that matter) to distinguish it from a regular identifier used elsewhere.that thing d definitely does my bad ```d import std; const int const(){return 3;} void main(){ const().writeln;// WORKS WOW its amazings how well petterc knows this language } ```
Nov 15
On Sunday, 16 November 2025 at 00:48:08 UTC, monkyyy wrote:On Saturday, 15 November 2025 at 23:55:47 UTC, Peter C wrote:Presumably, the D compiler knows when it is processing a token as part of a variable declaration vs as part of a return type. init int init() // Error: The token 'init' is not a valid qualifier for a function's return type. { return 42; }Then, 'init' would have a specific, unambiguous meaning when it precedes a type in a declaration, allowing the parser (and humans for that matter) to distinguish it from a regular identifier used elsewhere.that thing d definitely does my bad ```d import std; const int const(){return 3;} void main(){ const().writeln;// WORKS WOW its amazings how well petterc knows this language } ```
Nov 15
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:... 4. is this a waste of time chasing rainbows?No. I don't think so. It could be useful to forbid rebinding head in a linked list: module mymodule; private: import std.stdio; struct Node { int value; Node* next; } class LinkedList { // The old way: // Node* head; // The new way: // You want the binding (head) to be final, but the data // (the node and its links) to remain mutable. init Node* head; // binding is "once and done" this(int value) { head = new Node(value, null); // first assignment allowed } void append(int value) { Node* current = head; while (current.next !is null) { current = current.next; } current.next = new Node(value, null); } void print() { Node* current = head; while (current !is null) { writeln(current.value); current = current.next; } } } void main() { auto list = new LinkedList(10); list.append(20); list.append(30); list.append(40); writeln("Linked list contents:"); list.print(); // Uncommenting the next line would cause a compiler error: // You can mutate the list safely, but you can't throw away its root binding // list.head = new Node(99, null); // ERROR: cannot reassign 'head' }
Nov 15
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:Single assignment is the idea that a variable can only be assigned to once. This idea has been around for a while as recommended style, as in: [...] So, some food for thought: 1. do you use single assignment in D? (I try to use it.) 2. is const, with its transitivity, good enough? 3. if not good enough, is there a practical way for D to enforce it? 4. is this a waste of time chasing rainbows?If I understand this right, the idea is to make some variables effectively immutable without the `immutable` keyword? Why? This just seems like a recipe for chaos and complexity if so.
Nov 16
On Sunday, 16 November 2025 at 21:07:48 UTC, Clouudy wrote:... If I understand this right, the idea is to make some variables effectively immutable without the `immutable` keyword? Why? This just seems like a recipe for chaos and complexity if so.No, 'init' or 'final' or whatever it is called, on its own, is nothing more than a binding-level guarantee: the name x may be bound during initialization then not rebound later; it does not make the data of x immutable in any way whatsoever. Binding safety is fundamentally about correctness and preventing bugs, and not about immutability. e.g class Bag { init int[] items; // This is a binding-level guarantee - also acts an API invariant. this() { items = []; } } void main() { auto b = new Bag(); b.items ~= 1; // items is still mutable. b.items = []; // ERROR: Cannot assign to 'b.items' because it is has an init binding. }
Nov 16
On Sunday, 16 November 2025 at 22:53:16 UTC, Peter C wrote:
No, 'init' or 'final' or whatever it is called, on its own, is
nothing more than a binding-level guarantee: the name x may be
bound during initialization then not rebound later; it does not
make the data of x immutable in any way whatsoever.
Binding safety is fundamentally about correctness and
preventing bugs, and not about immutability.
e.g
class Bag
{
init int[] items; // This is a binding-level guarantee -
also acts an API invariant.
this() { items = []; }
}
void main()
{
auto b = new Bag();
b.items ~= 1; // items is still mutable.
b.items = []; // ERROR: Cannot assign to 'b.items' because
it is has an init binding.
}
I believe that the end result is still less flexibility than
before, so I still can't agree to it being forced on people, tbh
(unless I'm once again misunderstanding it).
Nov 27
On Thursday, 27 November 2025 at 16:45:23 UTC, Clouudy wrote:On Sunday, 16 November 2025 at 22:53:16 UTC, Peter C wrote:I'm not sure what you mean.. what would 'forced' on people? 'final' is for those **who want to use it** - typically to avoid a binding corruption, while still ensuring the object to which the variable is bound remains mutable. That is the 'specific' kind of flexibility this feature would provide. It is indeed about flexibility here. Although, if, as a user of someone elses code, it is 'forced' upon you, then that was the intent of the design (the API), and you should just accept it. Of course you can work around that if you really wanted to, but that would be your problem to deal with. The principle is very sound: Provide a mechanism to enforce single assignment (binding safety) without simultaneously introducing the side effect of preventing object mutation.No, 'init' or 'final' or whatever it is called, on its own, is nothing more than a binding-level guarantee: the name x may be bound during initialization then not rebound later; it does not make the data of x immutable in any way whatsoever. Binding safety is fundamentally about correctness and preventing bugs, and not about immutability. e.g class Bag { init int[] items; // This is a binding-level guarantee - also acts an API invariant. this() { items = []; } } void main() { auto b = new Bag(); b.items ~= 1; // items is still mutable. b.items = []; // ERROR: Cannot assign to 'b.items' because it is has an init binding. }I believe that the end result is still less flexibility than before, so I still can't agree to it being forced on people, tbh (unless I'm once again misunderstanding it).
Nov 27
On Thursday, 27 November 2025 at 22:50:55 UTC, Peter C wrote:I'm not sure what you mean.. what would 'forced' on people? 'final' is for those **who want to use it** - typically to avoid a binding corruption, while still ensuring the object to which the variable is bound remains mutable. That is the 'specific' kind of flexibility this feature would provide.Types are forced on people via apis; c apis with null strings are forcing that as a type. Any string is immutable in d due to the definition in object. It will happen simply will happen in some form.
Nov 27
On Thursday, 27 November 2025 at 22:50:55 UTC, Peter C wrote:I'm not sure what you mean.. what would 'forced' on people? 'final' is for those **who want to use it** - typically to avoid a binding corruption, while still ensuring the object to which the variable is bound remains mutable. That is the 'specific' kind of flexibility this feature would provide. It is indeed about flexibility here. Although, if, as a user of someone elses code, it is 'forced' upon you, then that was the intent of the design (the API), and you should just accept it. Of course you can work around that if you really wanted to, but that would be your problem to deal with. The principle is very sound: Provide a mechanism to enforce single assignment (binding safety) without simultaneously introducing the side effect of preventing object mutation.It was my impression that what they were planning on doing was making single assignment mandatory by default (as in, every variable would effectively be final). But after reading a bit more into it from the Discord that's not the plan, so I don't really have any complaints beyond maybe re-using keywords too much, or maybe that const/immutable could provide the same functionality (but this might be a worse idea).
Nov 28
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:Single assignment is the idea that a variable can only be assigned to once. This idea has been around for a while as recommended style, as in: [...]Nearly always, but sometimes the code is better if there's assignment. Rarely, but it happens.2. is const, with its transitivity, good enough?I use const/in everywhere unless I can't. I have no idea why people complain about it.
Nov 17
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:Single assignment is the idea that a variable can only be assigned to once. […] So, some food for thought: 1. do you use single assignment in D? (I try to use it.)I don’t use D professionally, unfortunately, but C++. I’ve written countless immediately invoked lambdas to have a delineated part of a function that takes care of the initialization of something that is then `const`. I even proposed `do` blocks in the DIP Ideas forum to make this easier. The mental load of a `const` thing is lower, which adds up in a larger function.2. is const, with its transitivity, good enough?Transitive `const` and shallow `const` are very different. The biggest issues with D’s non-transitive `const` is: - Newcomers over-apply it to function parameters, especially in generic code. - Interoperation with C and C++ is problematic because D can’t model `int* const`. This might be the single best reason to (re-?)introduce (something like) `final` as a qualifier in D with the meaning of shallow `const`.3. if not good enough, is there a practical way for D to enforce it?If a type has indirections, `const` might be overly restrictive. The `Final` template tried to do “not `const`, but no assignment,” but it fails miserably because for structs, it requires a new kind of member function that won’t mutate the shallow level, but can mutate the lower level.4. is this a waste of time chasing rainbows?One way to view this is suggesting people use `const` (or `immutable`) when they can and leave it to a linter otherwise. A linter might say something like this: ``` [Single assignment rule] Mutable local `x` is used almost as `const`, consider making it `const` 42 | int x = 10; ^ const ... 84 | x += 20; ^~~~~~~ Reassignment could introduce a new variable instead: | const x0 = x + 20; ``` Something like that. It’s a style thing. In some scenarios, there’s no good name for the new thing.
Nov 18
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:Single assignment is the idea that a variable can only be assigned to once. This idea has been around for a while as recommended style, as in: Bad style: ```d int a = 3; a = 4; ``` Good style: ```d int a = 3; int b = 4; ``` ... 4. is this a waste of time chasing rainbows?As you know, in Rust, variables are immutable by default. To allow reassignment, the mut (mutable) keyword must be used explicitly. This forces the programmer to consciously indicate when they intend to change a variable's value, providing strong support for the single assignment idiom. However, radically altering D's current structure to enforce mandatory single assignment could be akin to chasing rainbows, as it might go against the grain of the language's nature. The most practical approach is to utilise const/immutable and encourage this style through code reviews. SDB 79
Nov 22
On Friday, 14 November 2025 at 08:19:28 UTC, Walter Bright wrote:1. do you use single assignment in D? (I try to use it.)Yes, most variables are integers or strings, I just mark them as const.2. is const, with its transitivity, good enough?Yes. The case for single assignment is unclear. I can hypothesize that if you don't care about const correctness and your codebase has only mutable types, so const reference types are unusable, but you still want some constness, and more constness than const integers and strings; or you want to use it with complex inherently mutable types like mutex or buffered stream. meaningful and feels better.
Nov 24
On Monday, 24 November 2025 at 14:34:12 UTC, Kagamin wrote:The case for single assignment is unclear...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 24









Walter Bright <newshound2 digitalmars.com> 