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