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