On Sat, 30 Mar 2013 17:27:37 -0400, Jonathan M Davis <[email protected]> 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

Reply via email to