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