digitalmars.D - The point of const, scope, and other attributes
- Walter Bright (27/27) May 10 2022 The original D did not have them.
- Walter Bright (11/11) May 10 2022 The C++ checker did gripe about some of the debug printf format strings ...
- Dennis (4/6) May 11 2022 Never say never ;)
- Salih Dincer (5/10) May 10 2022 Very satisfying information that I have encountered for the first
- Dukc (18/19) May 11 2022 I am increasingly of the opinion that DIP1000 is too complicated
- IGotD- (8/12) May 11 2022 C# went the other way, no const parameters at all. Maybe because
- Walter Bright (4/10) May 11 2022 It's not simpler for someone trying to read the code. Function documenta...
- Fry (11/23) May 13 2022 Good documentation will. Really const doesn't help with making
- H. S. Teoh (16/30) May 13 2022 Yeah, more than one long-time D user (including myself) has come to this
- Tejas (5/24) May 13 2022 Both `auto ref` and `in` make a copy if it's an rvalue.
- =?UTF-8?Q?Ali_=c3=87ehreli?= (31/58) May 13 2022 'in' may not copy rvalues when compiled with -preview=in:
- Joseph Rushton Wakeling (28/30) May 14 2022 Maybe for a function that wants to define optional out parameters
- H. S. Teoh (13/33) May 14 2022 TBH, for cases like these, I'd just use a pointer and check for null:
- Joseph Rushton Wakeling (4/8) May 14 2022 Quite fair, of course. One doesn't _need_ mutable auto ref to
- Walter Bright (6/8) May 11 2022 My experience with non-transitive const in C and C++ is it is more
- jmh530 (41/43) May 11 2022 I'm generally pro-attribute, but I can't help but find myself
- Dennis (3/7) May 11 2022 I don't know what you mean with 'higher level of abstraction'
- jmh530 (19/26) May 11 2022 I don't know if I'm using it in the computer science way...
- Walter Bright (3/5) May 11 2022 The reason was does "ref scope return" mean return-ref scope, or does it...
- jmh530 (3/8) May 11 2022 I get that it was done for a reason...it's just a confusing
- Walter Bright (2/3) May 11 2022 The idea was to reduce the confusion!
The original D did not have them. Many people who have written D code without them, and then added them. Then they noticed that although those attributes are meant to prevent bugs, adding them to existing code did not find any bugs. So what is the point? A small anecdote. Before the backend was recently converted to D, I was invited to try out one of the good C++ bug checkers. I thought great, this will find bugs in the backend. While it generated about a thousand false positives, it didn't find a single bug. But I realized that I was running the checker over code that had already been thoroughly debugged through a lot of usage. Of course it didn't find anything. All the memory corruption bugs, bad casts, etc., had already been squeezed out. The point of the checker was to find bugs in fresh code, and find them before even trying to run the code. The checker saves you the trouble of finding those bugs yourself. It saves you the trouble caused by shipping those bugs. --- Another story. Back when I was programming in C, and was inexperienced, my code had a lot of bugs in it. I made every mistake, over and over. Having a memory corruption bug was a depressingly routine problem, and they were always hard to debug. But, over time, and endless experience, I gradually learned to avoid writing those bugs. I can't even recall the last time I wrote a memory corruption bug. (Of course, saying this, tomorrow for sure I'll make that mistake!) But this is really doing things the hard way. I'm lazy, I like the easy way. --- There are other reasons for the attributes, such as making code more understandable. A `const` parameter tells you the function doesn't modify that argument, or what it points to. A `scope` parameter tells you the function doesn't save a copy of it, so the caller can confidently delete the data. And so on.
May 10 2022
The C++ checker did gripe about some of the debug printf format strings not quite matching the arguments, such as printing a pointer with %x. The errors were benign, though. printf format mismatch was another situation where I wrote lots of bugs. But that experience helped push me towards getting D to do printf format checking. The checking doesn't find bugs anymore in existing code, because those errors never make it through a compilation. It also trained me to not make those mistakes anymore. So is that useful? Hell yes. I don't even have to bother checking the formats anymore when reviewing code. Neither need anyone else. Ain't that the shiznit? Why didn't we do that eons ago? Sigh.
May 10 2022
On Wednesday, 11 May 2022 at 05:15:00 UTC, Walter Bright wrote:The checking doesn't find bugs anymore in existing code, because those errors never make it through a compilation.Never say never ;) https://github.com/dlang/dmd/pull/13987 https://issues.dlang.org/show_bug.cgi?id=23020
May 11 2022
On Wednesday, 11 May 2022 at 05:02:31 UTC, Walter Bright wrote:There are other reasons for the attributes, such as making code more understandable. A `const` parameter tells you the function doesn't modify that argument, or what it points to. A `scope` parameter tells you the function doesn't save a copy of it, so the caller can confidently delete the data. And so on.Very satisfying information that I have encountered for the first time on subjects that I have never used, thank you. Maybe I'll use it more often now :) SDB 79
May 10 2022
On Wednesday, 11 May 2022 at 05:02:31 UTC, Walter Bright wrote:[snip]I am increasingly of the opinion that DIP1000 is too complicated for the average programmer to understand well. I find myself often stratching my head with it, it would be overwhelming for a beginner. Still, I do not advocate going back to pre-DIP1000 semantics because those are simply unsafe when it comes to slicing static arrays. I think we should start teaching two rules: 1: Never, ever brute force your way through DIP1000 errors with ` trusted` because you do not understand `return`, `scope` and `ref`. 2: If you're having trouble, GC-allocate the variables you're trying to use. The nice thing about DIP1000 is that one can't accidently introduce bugs as long as s/he follows rule 1. Regarding other attributes, I think it's good that our const/immutable/shared are transitive. Otherwise the attribute soup problem we have would be even worse.
May 11 2022
On Wednesday, 11 May 2022 at 07:21:02 UTC, Dukc wrote:I am increasingly of the opinion that DIP1000 is too complicated for the average programmer to understand well. I find myself often stratching my head with it, it would be overwhelming for a beginner.it doesn't make much sense because there is a lot of runtime better because it is simpler for the programmer. Another problem is that all this badging tend to get out of hand and it breeds more badges until it doesn't mean anything. For example what will happen if D adds a mutable attribute?
May 11 2022
On 5/11/2022 3:48 AM, IGotD- wrote:make much sense because there is a lot of runtime violations of const underneath.It's not simpler for someone trying to read the code. Function documentation almost never says "by the way, this `getValue` function also tweaks the database."Another problem is that all this badging tend to get out of hand and it breeds more badges until it doesn't mean anything. For example what will happen if D adds a mutable attribute?It already has a mutable attribute - nothing!
May 11 2022
On Wednesday, 11 May 2022 at 18:18:18 UTC, Walter Bright wrote:On 5/11/2022 3:48 AM, IGotD- wrote:Good documentation will. Really const doesn't help with making code more readable either. I avoided using const in D cause it just got in the way more than it was helping. The most useful feature for const to exist in C++ was that it allows you to do `const T&` which will allow you to pass anything to that function, rvalue or lvalue without making a copy. D doesn't do would have just added complexity without much benefit.because it doesn't make much sense because there is a lot of aproach better because it is simpler for the programmer.It's not simpler for someone trying to read the code. Function documentation almost never says "by the way, this `getValue` function also tweaks the database."I think he means a mutable attribute that allows you to modify a value even if the object is const. Like C++'s mutable.Another problem is that all this badging tend to get out of hand and it breeds more badges until it doesn't mean anything. For example what will happen if D adds a mutable attribute?It already has a mutable attribute - nothing!
May 13 2022
On Fri, May 13, 2022 at 06:25:22PM +0000, Fry via Digitalmars-d wrote:On Wednesday, 11 May 2022 at 18:18:18 UTC, Walter Bright wrote:[...]On 5/11/2022 3:48 AM, IGotD- wrote:Yeah, more than one long-time D user (including myself) has come to this conclusion. In my own experience, const is mainly useful at the lowest levels of code (strings, leaf-node modules that operate on some self-contained data structure that doesn't depend on anything else). Once you get to a high enough level of abstraction, it starts getting in your way and becomes far too much more work than the benefits it offers; it's just not worth the trouble. So these days I don't really bother with const, except in small pockets of leaf-node code.It's not simpler for someone trying to read the code. Function documentation almost never says "by the way, this `getValue` function also tweaks the database."Good documentation will. Really const doesn't help with making code more readable either. I avoided using const in D cause it just got in the way more than it was helping.The most useful feature for const to exist in C++ was that it allows you to do `const T&` which will allow you to pass anything to that function, rvalue or lvalue without making a copy. D doesn't do this though.[...] I thought `auto ref` was supposed to do this? But I recall people hating `auto ref` for various reasons... T -- Gone Chopin. Bach in a minuet.
May 13 2022
On Friday, 13 May 2022 at 18:43:11 UTC, H. S. Teoh wrote:On Fri, May 13, 2022 at 06:25:22PM +0000, Fry via Digitalmars-d wrote:Both `auto ref` and `in` make a copy if it's an rvalue. Idk why people hate `auto ref` though... most likely the fact that one might have to do `__traits(isRef, symbol)` anyways for some edge cases[...][...][...]Yeah, more than one long-time D user (including myself) has come to this conclusion. In my own experience, const is mainly useful at the lowest levels of code (strings, leaf-node modules that operate on some self-contained data structure that doesn't depend on anything else). Once you get to a high enough level of abstraction, it starts getting in your way and becomes far too much more work than the benefits it offers; it's just not worth the trouble. So these days I don't really bother with const, except in small pockets of leaf-node code.[...][...] I thought `auto ref` was supposed to do this? But I recall people hating `auto ref` for various reasons... T
May 13 2022
On 5/13/22 17:04, Tejas wrote:On Friday, 13 May 2022 at 18:43:11 UTC, H. S. Teoh wrote:'in' may not copy rvalues when compiled with -preview=in: import std.stdio; struct S(size_t N) { int[N] i; // Can be veeery big this (int i) { writeln("When constructing: ", &this.i); } } void foo(T)(in T s) { writeln("Inside foo : ", &s.i); } void main() { foo(S!1(42)); foo(S!1000(43)); } foo() receives two rvalue objects, second of which is passed by reference just because the compiler decided to do so: When constructing: 7FFD44B79EA0 Inside foo : 7FFD44B79E78 // <-- Copied for N==1; who cares. :) When constructing: 7FFD44B79EB0 Inside foo : 7FFD44B79EB0 // <-- rvalue passed by reference. That prooves D does indeed support rvalue references. :DOn Fri, May 13, 2022 at 06:25:22PM +0000, Fry via Digitalmars-d wrote:Both `auto ref` and `in` make a copy if it's an rvalue.[...][...][...]Yeah, more than one long-time D user (including myself) has come to this conclusion. In my own experience, const is mainly useful at the lowest levels of code (strings, leaf-node modules that operate on some self-contained data structure that doesn't depend on anything else). Once you get to a high enough level of abstraction, it starts getting in your way and becomes far too much more work than the benefits it offers; it's just not worth the trouble. So these days I don't really bother with const, except in small pockets of leaf-node code.[...][...] I thought `auto ref` was supposed to do this? But I recall people hating `auto ref` for various reasons... TIdk why people hate `auto ref` though... most likely the fact that one might have to do `__traits(isRef, symbol)` anyways for some edge casesMy discomfort with 'auto ref' is, it comes with a guideline: Treat the parameter 'const'. If not, an rvalue object would be mutated but the outside world would not know about it (mostly). Why would it be different for lvalues? Because they are passed by reference, the caller has an interest in the mutation, right? So, I can't imagine a use case where lvalue mutation is important but rvalue mutation is not. Ali
May 13 2022
On Saturday, 14 May 2022 at 01:11:03 UTC, Ali Çehreli wrote:So, I can't imagine a use case where lvalue mutation is important but rvalue mutation is not.Maybe for a function that wants to define optional out parameters that the caller can care about or not? It's a trivial example, but something like: ```D import std.stdio: writefln; void foo () (int input, auto ref int optional_output = 0 /* dummy rvalue default */) { writefln!"Input received: %s"(input); optional_output = input * 2; } void main() { foo(7); // `optional_out` parameter is effectively ignored writefln("Output parameter was not used"); int output; foo(19, output); // `optional_out` parameter writes to lvalue writefln!"Output received: %s"(output); assert(output == 38); } ``` A similar pattern might also be useful for passing optional context object to which a function might write, say, information on its progress. Not sure that either of these would be wise, but at least they are conceivable options.
May 14 2022
On Sat, May 14, 2022 at 09:34:51AM +0000, Joseph Rushton Wakeling via Digitalmars-d wrote:On Saturday, 14 May 2022 at 01:11:03 UTC, Ali Çehreli wrote:[...]So, I can't imagine a use case where lvalue mutation is important but rvalue mutation is not.Maybe for a function that wants to define optional out parameters that the caller can care about or not? It's a trivial example, but something like: ```D import std.stdio: writefln; void foo () (int input, auto ref int optional_output = 0 /* dummy rvalue default */) {} ``` A similar pattern might also be useful for passing optional context object to which a function might write, say, information on its progress.TBH, for cases like these, I'd just use a pointer and check for null: void foo()(int input, int* optional_output = null) { ... if (optional_output !is null) *optional_output = ...; ... } No need to fear pointers, they're in the language for a reason. T -- Build a man a fire, and he is warm for a night. Set a man on fire, and he is warm for the rest of his life.
May 14 2022
On Saturday, 14 May 2022 at 09:45:33 UTC, H. S. Teoh wrote:TBH, for cases like these, I'd just use a pointer and check for null [...] No need to fear pointers, they're in the language for a reason.Quite fair, of course. One doesn't _need_ mutable auto ref to achieve the use cases above ... but one could conceivably use it if one was so inclined.
May 14 2022
On 5/11/2022 12:21 AM, Dukc wrote:Regarding other attributes, I think it's good that our const/immutable/shared are transitive. Otherwise the attribute soup problem we have would be even worse.My experience with non-transitive const in C and C++ is it is more "documentation" than anything reliable. For example, if you're handing a reference to a non-trivial data structure to a function, there's no way to say "just read the data structure, don't change it". People schlepp a `const` on it anyway, but it doesn't work.
May 11 2022
On Wednesday, 11 May 2022 at 05:02:31 UTC, Walter Bright wrote:The original D did not have them. [snip]I'm generally pro-attribute, but I can't help but find myself scratching my head a bit with when to use return ref scope. And then for whatever reason there was a change so that now scope return has to be return scope. So I think it might help to lay out some principles vis a vis attributes. The first principle I would emphasize is "time to first plot". I learned about this from the Julia language, where they are talking about actual time it takes for Julia to boot up and be able to plot something. However, I think you can think about it a bit more metaphorically as "time for a new user to do something." This principle implies that D's opt-in approach to attributes is good since a new user can start doing work without needing to worry about them at first. On the basis of this principle, moving toward safe by default or const/immutable by default would be bad. The second principle is harder to describe, but it is related to the relationship between complexity, levels of abstraction, and mental burden. In the back of my head, I am recalling a chart that compared C++ and D with D listed as less complex than C++ (I don't recall what the other axis was, maybe something like expressive power). Anyway, if a programming language is considering adding a feature and there is a less complex version that abstracts away from some details versus a more complex version that operates on a higher level of abstraction, then it can be the case that the more complex version operating on a higher level of abstraction might actually have a lower mental burden. Consider the treatment of lifetimes in DIP 1000 vs lifetimes in Rust. DIP 1000's use of return probably covers most peoples' use cases, but might also require a higher mental burden to figure out exactly what it means since it is less abstract. By contrast, Rust lifetimes are implemented in a more complex way, but also operating at a higher level of abstraction. The higher level of abstraction might also result in a lower mental burden. Long story short, sometimes adding a more complex feature can be a good thing if it comes associated with a higher level of abstraction. Of course, the trade off between the complexity and the ability to generate improved performance/safety is another important principle.
May 11 2022
On Wednesday, 11 May 2022 at 12:54:31 UTC, jmh530 wrote:By contrast, Rust lifetimes are implemented in a more complex way, but also operating at a higher level of abstraction. The higher level of abstraction might also result in a lower mental burden.I don't know what you mean with 'higher level of abstraction' here, can you give an example?
May 11 2022
On Wednesday, 11 May 2022 at 13:56:32 UTC, Dennis wrote:On Wednesday, 11 May 2022 at 12:54:31 UTC, jmh530 wrote:I don't know if I'm using it in the computer science way... I mean it like how if you look at the rules of arithmetic (distributive, associative, etc.) these apply to what we normally think of as numbers (integers, reals, etc.) but then they apply different to other things (like matrices). You can abstract from these ideas to group theory and rings and make statements about all algebras with particular rules. So with respect to lifetimes, in D we can apply the return attribute to a function parameter. From Rust's perspective, this is equivalent to an explicit lifetime for a function parameter that lasts as long as the return. I was phrasing it as the Rust approach is a higher level of of abstraction, but you could also think of it as the D approach is a subset of the Rust approach. I think this was done on purpose, acknowledging that the return annotation is the most often way Rust lifetimes are used. My response would be that is all well and fine, but I still find this confusing: https://dlang.org/spec/function.html#ref-return-scope-parametersBy contrast, Rust lifetimes are implemented in a more complex way, but also operating at a higher level of abstraction. The higher level of abstraction might also result in a lower mental burden.I don't know what you mean with 'higher level of abstraction' here, can you give an example?
May 11 2022
On 5/11/2022 5:54 AM, jmh530 wrote:And then for whatever reason there was a change so that now scope return has to be return scope.The reason was does "ref scope return" mean return-ref scope, or does it mean ref return-scope?
May 11 2022
On Wednesday, 11 May 2022 at 18:21:29 UTC, Walter Bright wrote:On 5/11/2022 5:54 AM, jmh530 wrote:I get that it was done for a reason...it's just a confusing reason...And then for whatever reason there was a change so that now scope return has to be return scope.The reason was does "ref scope return" mean return-ref scope, or does it mean ref return-scope?
May 11 2022
On 5/11/2022 4:07 PM, jmh530 wrote:I get that it was done for a reason...it's just a confusing reason...The idea was to reduce the confusion!
May 11 2022