On Saturday, March 30, 2013 08:18:08 Andrej Mitrovic wrote: > On 3/30/13, Jonathan M Davis <[email protected]> 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
