Rant: Date and Time fall short of simplicity in D

Jonathan M Davis jmdavisProg at gmx.com
Sat Mar 30 00:33:22 PDT 2013


On Saturday, March 30, 2013 08:18:08 Andrej Mitrovic wrote:
> On 3/30/13, Jonathan M Davis <jmdavisProg at gmx.com> wrote:
> > If opCast is defined, it's perfectly
> > safe regardless of what would happen if you tried to cast without opCast
> > being defined.
> 
> Casting is generally unsafe when working with reference types like
> classes. For example:
> 
> import std.stdio;
> final class A
> {
>     B opCast()
>     {
>         return new B;
>     }
> }
> 
> class B
> {
>     void bar() { writeln("B.bar"); }
> }
> 
> void main()
> {
>     A a = new A;
>     auto b = cast(B)a;
>     b.bar();  // no problem
> }
> 
> Now, remove the opCast, recompile and run and you'll get a segfault.
> 
> So a cast is not safe, but you could try using std.conv.to in user-code:
> 
> void main()
> {
>     A a = new A;
>     auto b = to!B(a);
>     b.bar();
> }
> 
> This will now throw an exception at runtime. But, notice how it didn't
> catch the bug at compile-time. std.conv.to might even catch it at
> compile-time if it knows the class doesn't inherit from any
> user-defined classes, but generally it can't avoid using a dynamic
> cast in a tree hierarchy. It will look for an opCast first, and then
> try to do a dynamic cast.
> 
> But this is safer and can be caught at compile-time:
> 
> import std.stdio;
> 
> final class A
> {
>     T toType(T)()
>         if (is(T == B))
>     {
>         return new B;
>     }
> }
> 
> class B
> {
>     void bar() { writeln("B.bar"); }
> }
> 
> T to(T, S)(S s)
>     if (__traits(hasMember, S, "toType"))
> {
>     return s.toType!T();
> }
> 
> void main()
> {
>     A a = new A;
>     auto b = to!B(a);
>     b.bar();
> }
> 
> If you remove toType, you will get a compile-time error instead of a
> runtime one.
> 
> The point is to 1) Avoid using casts in user code, and 2) Avoid using
> std.conv.to because it's too magical (tries opCast before trying
> dynamic cast, which may not be what we want).
> 
> There needs to be an explicit handshake between what the library type
> provides and what the user wants, cast is just too blunt and is a
> red-flag in user-code.

Having std.conv.to use toType wouldn't help any. You'd be in exactly the same 
situation as long as std.conv.to will do dynamic cast. It's just that when the 
type didn't define the conversion function for std.conv.to, instead of not 
defining opCast, the type would not be defining toType. Safety would completely 
unaffected.

To get what you're looking for, you'd need a conversion function that only 
ever had one way to convert types, whereas std.conv.to has a whole host of 
ways to convert types. But in the case of structs, odds are that it won't work 
without opCast being defined, and classes are essentially the same except for 
the exception that you point out, so std.conv.to and opCast very nearly get 
what you want anyway. The main hangup is that it'll try dynamic casting class 
references if opCast isn't defined, and to get what you want, that would have 
to not be permitted, which obviously isn't going to happen with std.conv.to.

But I really don't buy that defining or using opCast is particularly dangerous, 
and I don't think that it's problem at all that std.conv.to uses that instead 
of toType or anything other specific conversion function, since it's explicitly 
checking that opCast is defined before it uses it (the code that does the 
dynamic cast on class references is a completely different overload). I think 
that you're concerned about something that really isn't a problem.

- Jonathan M Davis


More information about the Digitalmars-d mailing list