Rant: Date and Time fall short of simplicity in D
Steven Schveighoffer
schveiguy at yahoo.com
Sat Mar 30 20:24:37 PDT 2013
On Sat, 30 Mar 2013 17:27:37 -0400, Jonathan M Davis <jmdavisProg at gmx.com>
wrote:
> On Saturday, March 30, 2013 09:53:49 Artur Skawina wrote:
>> You can't tell if there is an opCast w/o looking at S. So it's either
>> a perfectly fine conversion, no-op, a potentially dangerous operation,
>> or
>> an error. The compiler would have been able to catch the error, but by
>> overloading 'cast' you've prevented it from doing that.
>
> It would only be an error because the compiler couldn't do the cast (and
> I
> believe that unless the memory layout is the same, casting between two
> structs
> without opCast doesn't work, and classes will only give an error if
> neither
> class is a base class of the other), so defining opCast eliminates any
> need for
> any error.
>
> But regardless, if you use std.conv.to rather than casting directly,
> then you
> don't have to worry about whether opCast is defined or not. If it is or
> std.conv.to can convert the type in another way, then std.conv.to will
> work,
> and if there is no opCast and none of std.conv.to's conversions will
> work,
> then you'll get an error. It won't try to explicitly cast unless the
> type has
> defined opCast.
You continue to miss the point. The problem is NOT std.conv.to directly.
The problem is that opCast can be used by typing the dangerous keyword
cast.
It is perfectly fine that std.conv.to uses opCast, especially if it does
so in a safe manner by calling the method directly.
BUT... if you define opCast, you ALSO define another API on your type, one
that uses the dangerous keyword cast. Unless there is a good reason, I
don't think anyone should do this. The good reason should NOT be "so it
will work with std.conv.to". There should be another way. In other
words, I want to define a way for my type to convert to some other type
that std.conv.to can use, and NOT define it via cast.
Here is an admittedly contrived example of why it is bad:
struct S
{
int *x;
}
struct T
{
int *x;
U opCast(U)() if(is(U == S)) { return S(x); }
}
This cast is perfectly safe to use. It correctly rejects attempts to
remove const or immutable. Great!
Now, we define a function foo, which takes an S:
void foo(S s)
{
*s.x = 5;
}
But, hey, why not allow any type that can CONVERT to S?
void foo(X)(X x)
{
auto s = cast(S)x;
*s.x = 5;
}
Now foo works with S or T, just fine! It fails with const(T) or
immutable(T) because opCast isn't const or immutable!
But do you see the problem here? What happens with this?
immutable int x = 4;
auto s = immutable(S)(&x);
foo(s);
does this compile? YES IT DOES. And it changes the value of x to 5.
Because 'cast' not only invokes user-defined opCast, but ALSO invokes the
compiler's very dangerous "disregard type-safety checks" cast. We need to
specifically disable this version! It's *extra* code to make it safe, and
the author may not even realize what he did!
The indirect issue here is that std.conv.to should not be promoting using
opCast, due to the inherent dangers of using cast. Yes, you can define
opCast, and yes, you can use it only via std.conv.to. But you have now
left a dangling hook inviting an unsuspecting coder to use cast on your
type. There is no reason for that, and we shouldn't have that requirement
to hook std.conv.to.
I would argue, actually, that to(T)() should be the method that
std.conv.to can hook. Simply due to the advent of UFCS:
import std.conv;
a.to!B
will invoke a's specific to!B function if available, or std.conv.to!B(a)
otherwise. I can still use the member without importing, and generic
code that directly invokes std.conv.to will function (just call a's to
method).
let's not standardize on cast, let's standardize on to! It's already the
safer choice.
Note, we don't have to break ANY code to make this change. We simply have
to define 'to' in addition to types that already have opCast. Eventually
we may deprecate opCast on those types, but I don't see the point -- when
people see the to function, they will use it instead of casting (we can
also recommend not using it via documentation). Especially if most types
DON'T define opCast, to will simply become more natural. You also don't
need to understand the compiler cast rewriting to use a 'to' method.
-Steve
More information about the Digitalmars-d
mailing list