User Defined Attributes

bearophile bearophileHUGS at lycos.com
Tue Nov 6 10:40:24 PST 2012


Walter Bright:

First of all, assume I know how to use and abuse foreach in all 
the common and uncommon cases. So in this discussion we can 
assume we both know well enough what we are talking about. What I 
am discussing about is a real problem in real D code, a bug-prone 
characteristic of foreach. We may accept the situation like it 
is, and do nothing, or we may want to improve the situation. But 
this doesn't change the fact that there is a problem in the D 
design of foreach loops when you scan an array of structs. This 
problem is recognized by several other persons in the D 
community, it's not a creation of my mind.

That said, let me try again to explain the problem.

>> // Case3 (uncommon)
>> void main() {
>>     auto data = [0, 1, 2, 3];
>>     foreach (@copy x; data)
>>         writeln(++x);
>> }
>
> x is a value type, and it is already copied. And if you did 
> want to copy it,
>
>     foreach (x; data)
>     {   auto copy = x;
>         writeln(++copy);
>     }
>
> I don't see what the annotation buys you, even if it could be 
> made to work.

The point of Case3 is not that I want to copy it again. The 
annotation by itself buys you nothing. But he annotation is not 
meant to be the only change in D. There are two changes that need 
to happen at the same time:


Change1) Make this a compile-time error, this means if you write 
this code, the compiler refuses your code statically. So "x" 
despite not being const is like being const, because the compiler 
does not let you modify it inside the loop, if you try, it gives 
you an error (so maybe it's simpler to implement this Change1 
really as putting "const" on default on x even if you don't write 
"const" there. C# language is designed this way!):

auto data = [1, 2, 3];
foreach (x; data)
     x++; // compilation error here


Change2) Introduce an annotation like @mutable to denote Case3 (I 
have replaced @copy with @mutable to make this explanation more 
clear for you, so maybe @mutable is really a better name for this 
idea). This means this is accepted, it's like saying that x is 
not const:

foreach (@mutable x; data)
     writeln(++x); // OK



Why I have suggested this pair of changes? The only purpose of 
those two changes is to remove one bug-prone situation from D 
code.

The situation is this:

struct Foo { int x; }
auto data = [Foo(1), Foo(2), Foo(3)];
foreach (f; data)
     f.x++;


What is the programmer trying to write there? Was she trying to 
change the structs inside data (Case2)? Or was she trying to 
modify just their copy (Case3)?

The problem is that usually programmers want Case1 or Case2, they 
most times do not want to write a Case3. But it's very easy to 
forget "ref". So the programmer wants to write a Case2 but often 
writes a Case3 by mistake.

The combined presence of Change1 and Change2 removes this source 
of errors. Because now if you forget "ref" the compiler refuses 
your code statically. If you want Case3 you add a @copy. In most 
cases you really meant to use a Case2 so you add "ref" and the D 
compiler has caught your bug!

If you were trying to write a Case1 you didn't put inside the 
loop code that modifies the struct, so the error of Change1 is 
not triggered and you need no @copy nor ref.


I have written probably 250_000+ lines of good-quality D1/D2 
code, and bugs like this are still present in my code, so I'd 
like to remove them from D once and for all:

struct Foo { int x; }
auto data = [Foo(1), Foo(2), Foo(3)];
foreach (f; data)
     f.x++;

Bye,
bearophile


More information about the Digitalmars-d-announce mailing list