import core.thread;
import core.time;
import std.datetime;
import std.stdio;
void main()
{
StopWatch sw;
sw.start();
Thread.sleep(dur!"seconds"(1));
Thread.sleep(dur!"msecs"(200));
Thread.sleep(dur!"usecs"(800));
sw.stop();
TickDuration time = sw.peek();
long secs = time.seconds;
long msecs = time.msecs - (1000 * secs);
long usecs = time.usecs - (1000 * msecs) - (1000 * 1000 * secs);
writefln("secs: %s, msecs: %s, usecs: %s", secs, msecs, usecs);
}
The above works, but it's really messy having to do this all by hand.
So the other workaround is:
TickDuration time = sw.peek();
long secs = time.seconds;
time -= cast(TickDuration)dur!"seconds"(secs);
long msecs = time.msecs;
time -= cast(TickDuration)dur!"msecs"(msecs);
long usecs = time.usecs;
Note that the casts are necessary since there is no "tickdur!()"
function and a TickDuration cannot be subtracted with a Duration (I
don't understand why it's implemented like this). The whole date and
time API is extremely non orthogonal and ugly in D.
For example, Duration has seconds, msecs, usecs as property functions
but they are all calculated as the duration minus the larger units, so
1.200 seconds means that time.seconds == 1 && time.msecs == 200. But
TickDuration uses a different API where time.seconds == 1 &&
time.msecs == 1200.
Why was this inconsistency introduced?
--
The documentation for various property functions was clearly
copy-pasted, take a look for example at these:
/++
The value of this $(D FracSec) as milliseconds.
+/
property int msecs() safe const pure nothrow;
/++
The value of this $(D FracSec) as milliseconds.
Params:
milliseconds = The number of milliseconds passed the second.
Throws:
$(D TimeException) if the given value is not less than $(D 1) second
and greater than a $(D -1) seconds.
+/
property void msecs(int milliseconds) safe pure;
The second property function is a setter, it should be documented as:
"Sets the value of this $(D FracSec) to $(B milliseconds) number of
milliseconds."
I've seen this type of property documentation copied everywhere in
date/time-related structures and classes, it's not clear from the
documentation that these are setters, they should be documented as
such.
---
Another example, I once had to convert a long type which represented
Unix time into DateTime. Here's the code to do it:
return cast(DateTime)SysTime(unixTimeToStdTime(cast(int)d.when.time));
It's unbelievable how ugly this is, and it took me way over half an
hour searching the docs and trying various stuff out to figure this
out.
---
Anyway, maybe time and datetime are power-houses in Druntime and
Phobos, but they trade their features for simplicity.
Perhaps the real problem is the documentation, or the actual layout of
the API itself. The API seems to contain a ton of functionality, and
maybe the more specialized functions should be moved into separate
modules (and make datetime be part of its own package).
The docs for std.datetime for example are huge.

This one is VERY annoying, Duration has some of the necessary properties.
The sub-second values you must obtain with FracSecs, and once you get
there, it does NOT give you properties which remove the higher units.
So I can't really do this any better than you did.
core.time needs to be fixed. Duration is really what you should use when
doing duration math. I don't know why TickDuration still exists actually.

Why was this inconsistency introduced?

Well, Duration was the basic type for general date time stuff that
Jonathan came up with. TickDuration was created for StopWatch, which was
developed by Kato Shoichi.
IMO, they should be combined.

Another example, I once had to convert a long type which represented
Unix time into DateTime. Here's the code to do it:
return cast(DateTime)SysTime(unixTimeToStdTime(cast(int)d.when.time));

I have three comments here:
1. unixTimeToStdTime should take ulong.
2. There should be a shortcut for this.
Note on Windows, given a SYSTEMTIME we can do:
return cast(DateTime)SYSTEMTIMEToSysTime(t);
We need an equivalent unixTimeToSysTime, and in fact, I think we can get
rid of unixTimeToStdTime, what is the point of that?
3. I HATE "safe" cast conversions. If you want to make a conversion, use
a method/property. I don't even know why D allows overloading casting.
Casts are way too blunt for this.
The code should be:
return unixTimeToSysTime(d.when.time).asDateTime;

Anyway, maybe time and datetime are power-houses in Druntime and
Phobos, but they trade their features for simplicity.
Perhaps the real problem is the documentation, or the actual layout of
the API itself. The API seems to contain a ton of functionality, and
maybe the more specialized functions should be moved into separate
modules (and make datetime be part of its own package).
The docs for std.datetime for example are huge.

...
3. I HATE "safe" cast conversions. If you want to make a conversion,
use a method/property. I don't even know why D allows overloading
casting. Casts are way too blunt for this.
...

The only conceivable reason is opCast!bool, eg. for use in if
conditions.

(In case this is not clear, a conversion method cannot really do the job
satisfactorily because of the if(auto x = foo()) construct.)

That one I don't mind, because it is an implicit cast. I think that
feature actually would be better served by opBool or opTest or something
like that.
It's the explicit casts which are horrible. Cast makes type-checking go
away to some degree, it's like requiring a sledge hammer to put in finish
nails. Plus the syntax is awkward.
-Steve

This one is VERY annoying, Duration has some of the necessary properties.
The sub-second values you must obtain with FracSecs, and once you get
there, it does NOT give you properties which remove the higher units.
So I can't really do this any better than you did.

Clearly, I need to take another look at FracSec then.

core.time needs to be fixed. Duration is really what you should use when
doing duration math. I don't know why TickDuration still exists actually.

I tend to agree. We _do_ need a separate time type which is in system ticks
for the monotonic clock, but the stopwatch stuff doesn't need to use it. I'd
fix
it, but I don't know if we can do so without breaking code. I'll have to think
about it.

3. I HATE "safe" cast conversions. If you want to make a conversion, use
a method/property. I don't even know why D allows overloading casting.
Casts are way too blunt for this.

It's how you define coversions to work with std.conv.to, so you could do
to!DateTime(sysTime), which is shorter and avoids the explicit cast in your
code (though the cast still occurs within std.conv.to).
- Jonathan M Davis

3. I HATE "safe" cast conversions. If you want to make a conversion,
use
a method/property. I don't even know why D allows overloading casting.
Casts are way too blunt for this.

It's how you define coversions to work with std.conv.to, so you could do
to!DateTime(sysTime), which is shorter and avoids the explicit cast in
your
code (though the cast still occurs within std.conv.to).

This seems like a horrible round-about way of creating a method:
1. Define opCast!OtherType
2. import std.conv
3. instead of using obj.toOtherType, use to!OtherType(obj)
Do we really need to go through this mess? I understand the point is for
'to' to be usable in generic code, but can't to be made to call the
correct method instead of using cast? I don't really like to using cast
in this case anyway.
What about something like (omitting necessary constraints in the interest
of brevity):
T to(T, U)(U u)
{
return mixin!("u.to" ~ T.stringof);
}
Or at least just alias the opCast to the method.
-Steve

3. I HATE "safe" cast conversions. If you want to make a conversion,
use
a method/property. I don't even know why D allows overloading casting.
Casts are way too blunt for this.

It's how you define coversions to work with std.conv.to, so you could do
to!DateTime(sysTime), which is shorter and avoids the explicit cast in
your
code (though the cast still occurs within std.conv.to).

This seems like a horrible round-about way of creating a method:
1. Define opCast!OtherType
2. import std.conv
3. instead of using obj.toOtherType, use to!OtherType(obj)
Do we really need to go through this mess? I understand the point is for
'to' to be usable in generic code, but can't to be made to call the
correct method instead of using cast? I don't really like to using cast
in this case anyway.
What about something like (omitting necessary constraints in the interest
of brevity):
T to(T, U)(U u)
{
return mixin!("u.to" ~ T.stringof);
}
Or at least just alias the opCast to the method.

It used to be that std.conv.to used to on the type, but it was decided to get
rid of that in favor of using opCast. I don't remember all of the reasons for
it though.
But std.conv.to is the standard way to convert things, and I don't see how
changing how std.conv.to determines how to do the conversion would help us
any. Whether there was a to function on the type or opCast really makes no
difference if you're using std.conv.to, and if you're not, then the way that
the language provides to covert types - casting - works.
Unless you're arguing for using something other than std.conv.to to convert
types, I really don't see the problem, and arguably, because std.conv.to is
really the standard way to convert stuff, it's what should be used. So, I could
see a definite argument for using std.conv.to in code rather than opCast, but I
don't see much point in avoiding defining opCast on types, especially if code
is then generally using std.conv.to rather than casting directly.
- Jonathan M Davis

But std.conv.to is the standard way to convert things, and I don't see
how
changing how std.conv.to determines how to do the conversion would help
us
any. Whether there was a to function on the type or opCast really makes
no
difference if you're using std.conv.to, and if you're not, then the way
that
the language provides to covert types - casting - works.
Unless you're arguing for using something other than std.conv.to to
convert
types, I really don't see the problem, and arguably, because std.conv.to
is
really the standard way to convert stuff, it's what should be used. So,
I could
see a definite argument for using std.conv.to in code rather than
opCast, but I
don't see much point in avoiding defining opCast on types, especially if
code
is then generally using std.conv.to rather than casting directly.

When I say "cast(Duration)time is ugly and dangerous" you say, "use
std.conv.to instead." Why?
It seems you are using std.conv.to as part of the API of core.time types.
I can't really understand the point of this. There exists a safe and
necessary conversion (since both provide different features) from a
TickDuration to a Duration. Why would that be an obscure part of the
API? Why would the preferable interface be to use a cast? Why does
std.conv.to have to be involved to get something readable that doesn't
contain the red-flag cast operator? Both TickDuration and Duration know
about each other, there is no reason to make this a dangerous operation
(and yes, casts are dangerous and should be avoided).
It looks to me like the only reason a cast was chosen over a
property/method is *so* it will work with std.conv.to. I contend that it
would be better of std.conv.to was not able to convert these types than to
have to use cast on it to get this behavior.
If std.conv.to cannot work on type-defined conversions without opCast,
then it is poorly implemented. There needs to be a better mechanism.
-Steve

I think you are applying "common knowledge" to something where it
doesn't apply.
auto foo = cast(TickDuration) bar;
This is not unsafe, unless you claim that opCast is being
implemented in a dangerous manner (takes a Variant and converts
it to 32)
I'm really confused on what your argument is. It sounds like you
don't want to!() to be able and convert user types, but if it
does than it shouldn't be allowed to use cast.
I'm not sure what issue you are expecting to prevent by requiring
bar.toTickDuration() instead of using cast or to!().

I think you are applying "common knowledge" to something where it
doesn't apply.
auto foo = cast(TickDuration) bar;
This is not unsafe, unless you claim that opCast is being implemented in
a dangerous manner (takes a Variant and converts it to 32)

Well, when you cast, you can inadvertently remove const, shared,
immutable, etc. In these cases, cast is safe, but refactoring can make
things unsafe. There are certain cases with cast where it happily
discards const or immutable without so much as a peep. Changing the type
of bar above to const TickDuration, for example, could allow code that was
not intended to discard const to do so.
In the case of TickDuration, it is a purely-value type, so it's not an
issue. But something with a reference could behave badly. If you
accidentally end up invoking the *compiler* cast, the type system goes out
the window.

I'm really confused on what your argument is. It sounds like you don't
want to!() to be able and convert user types, but if it does than it
shouldn't be allowed to use cast.

No, that's not what I'm saying. I think the *mechanism* for 'to' to
convert types should be something other than cast. At least it should be
allowed.

I'm not sure what issue you are expecting to prevent by requiring
bar.toTickDuration() instead of using cast or to!().

I want to *allow* bar.toTickDuration(), and if you want to use
to!TickDuration(), that should be fine too. But to REQUIRE
cast(TickDuration) or importing another module to access the API in a safe
manner should not be considered good practice. A cast should signal a
red-flag for code reviews, it should not be a mundane API call.
-Steve

But std.conv.to is the standard way to convert things, and I don't see
how
changing how std.conv.to determines how to do the conversion would help
us
any. Whether there was a to function on the type or opCast really makes
no
difference if you're using std.conv.to, and if you're not, then the way
that
the language provides to covert types - casting - works.
Unless you're arguing for using something other than std.conv.to to
convert
types, I really don't see the problem, and arguably, because std.conv.to
is
really the standard way to convert stuff, it's what should be used. So,
I could
see a definite argument for using std.conv.to in code rather than
opCast, but I
don't see much point in avoiding defining opCast on types, especially if
code
is then generally using std.conv.to rather than casting directly.

When I say "cast(Duration)time is ugly and dangerous" you say, "use
std.conv.to instead." Why?
It seems you are using std.conv.to as part of the API of core.time types.
I can't really understand the point of this. There exists a safe and
necessary conversion (since both provide different features) from a
TickDuration to a Duration. Why would that be an obscure part of the
API? Why would the preferable interface be to use a cast? Why does
std.conv.to have to be involved to get something readable that doesn't
contain the red-flag cast operator? Both TickDuration and Duration know
about each other, there is no reason to make this a dangerous operation
(and yes, casts are dangerous and should be avoided).
It looks to me like the only reason a cast was chosen over a
property/method is *so* it will work with std.conv.to. I contend that it
would be better of std.conv.to was not able to convert these types than to
have to use cast on it to get this behavior.

std.conv.to is the standard way to convert one type to another. I see no
reason to introduce stuff specific to core.time or std.datetime to do
conversions. It should just hook into the standard stuff for that. If
everything uses std.conv.to for coverting between types, then you don't have
to worry about figuring out how a particular programmer decided that their API
should do it - be it with casts or asOtherType toOtherType or whatever.
std.conv.to is specifically designed so that any type can hook their own
conversions into it, and then you can just always use std.conv.to for
converting types.

If std.conv.to cannot work on type-defined conversions without opCast,
then it is poorly implemented. There needs to be a better mechanism.

I don't see why. std.conv.to specifically checks for opCast, not just that it
can cast. So, there's nothing unsafe about it. Having it look for a function
named convert wouldn't be any safer.
The only reason I see to object to opCast being used is that it's then
possible to use cast(Type) rather than to!Type, and if you object to casts
being used that way, then having std.conv.to use opCast makes it more likely
that cast(Type) will work, because people will define it on their types so that
they'll work with std.conv.to.
But since opCast is really just syntactic sugar that allows you to use the
cast operator, and casting rarely works on user-defined types without opCast
anyway (aside from converting between classes in an inheritance hierarchy), I
really don't agree that opCast is particularly dangerous. If opCast isn't
defined, odds are the cast won't work. And if it is, then there's really no
difference between using the cast operator and an explicit function except that
by using the cast operator, you're plugging into the language's conversion
mechanism syntactically, and std.conv.to will then work for your type. And if
you prefer std.conv.to to casting, then just use std.conv.to.
But the built-in casts are restricted enough on user-defined types, that I
really don't see any problem with using opCast on user-defined types and then
casting, and std.conv.to goes the extra mile of only using the cast if opCast
is explicitly defined, so it won't use any dangerous casts even if there are
any.
- Jonathan M Davis

std.conv.to is the standard way to convert one type to another. I see no
reason to introduce stuff specific to core.time or std.datetime to do
conversions. It should just hook into the standard stuff for that. If
everything uses std.conv.to for coverting between types, then you don't
have
to worry about figuring out how a particular programmer decided that
their API
should do it - be it with casts or asOtherType toOtherType or whatever.
std.conv.to is specifically designed so that any type can hook their own
conversions into it, and then you can just always use std.conv.to for
converting types.

But the one doing the work is core.time. In essence, you have locked away
part of the API behind cast, and in order to get it out without using
cast, you have to import another module.
opCast is just a function, it could easily be called opTo, or simply
to(T)().

If std.conv.to cannot work on type-defined conversions without opCast,
then it is poorly implemented. There needs to be a better mechanism.

I don't see why. std.conv.to specifically checks for opCast, not just
that it
can cast. So, there's nothing unsafe about it. Having it look for a
function
named convert wouldn't be any safer.

Right, it's not std.conv.to that is the problem, it's the fact that you
then have to expose your type to possible arbitrary casting. One mistake,
or refactor, and you have have thrown away const inadvertently.
There should be a safer way to hook 'to'.

The only reason I see to object to opCast being used is that it's then
possible to use cast(Type) rather than to!Type, and if you object to
casts
being used that way, then having std.conv.to use opCast makes it more
likely
that cast(Type) will work, because people will define it on their types
so that
they'll work with std.conv.to.

This is exactly my objection. People (like the OP in this thread) don't
think about opCast being specifically for use with std.conv.to, they just
use it as cast(X), which can be dangerous.

But since opCast is really just syntactic sugar that allows you to use
the
cast operator, and casting rarely works on user-defined types without
opCast
anyway (aside from converting between classes in an inheritance
hierarchy), I
really don't agree that opCast is particularly dangerous. If opCast isn't
defined, odds are the cast won't work. And if it is, then there's really
no
difference between using the cast operator and an explicit function
except that
by using the cast operator, you're plugging into the language's
conversion
mechanism syntactically, and std.conv.to will then work for your type.
And if
you prefer std.conv.to to casting, then just use std.conv.to.

I've already found problems with std.conv.to and arbitrary casting. See
this bug:
http://d.puremagic.com/issues/show_bug.cgi?id=6288
If you aren't careful, you can easily end up casting away const without
intending to. If phobos can get it wrong, so can average developers.

But the built-in casts are restricted enough on user-defined types, that
I
really don't see any problem with using opCast on user-defined types and
then
casting, and std.conv.to goes the extra mile of only using the cast if
opCast
is explicitly defined, so it won't use any dangerous casts even if there
are
any.

The issue is when you think you are invoking the opCast operator, but you
inadvertently end up casting using the compiler's type-bypassing version.
I agree the opCast call is safe, it's that its name coincides with the
"throw all typechecks away" operator.
I don't think to should ignore opCast, or not use it, but there should be
a way to hook 'to' without using opCast. And most types should prefer
that.
-Steve

The issue is when you think you are invoking the opCast operator, but you
inadvertently end up casting using the compiler's type-bypassing version.
I agree the opCast call is safe, it's that its name coincides with the
"throw all typechecks away" operator.

I'd have to experiment to see exactly what is and isn't accepted, but in my
experience, the compiler rarely allows casting to or from structs without
opCast (the same with classes except for the inheritance tree). So, I really
don't think that there's much risk of accidentally using a cast on a user-
defined type and have it use the built-in cast operator.

I don't think to should ignore opCast, or not use it, but there should be
a way to hook 'to' without using opCast. And most types should prefer
that.

I really just don't see a problem here. If opCast is defined, it's perfectly
safe regardless of what would happen if you tried to cast without opCast being
defined. It's also the language-defined way to do type conversions. And I
really
don't see any need to use anything else to make std.conv.to work. By using
opCast, there's a standard way to define type conversion, and there's a
standard way for it to hook into std.conv.to, which seems way better to me
than trying to support every which way that a particular programmer wants to
try and define a conversion function. Clearly, you think that using opCast is a
problem, but I just don't agree. It's safe; it's standard; and it works.
- Jonathan M Davis

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.

The issue is when you think you are invoking the opCast operator, but you
inadvertently end up casting using the compiler's type-bypassing version.
I agree the opCast call is safe, it's that its name coincides with the
"throw all typechecks away" operator.

I'd have to experiment to see exactly what is and isn't accepted, but in my
experience, the compiler rarely allows casting to or from structs without
opCast (the same with classes except for the inheritance tree). So, I really
don't think that there's much risk of accidentally using a cast on a user-
defined type and have it use the built-in cast operator.

I don't think to should ignore opCast, or not use it, but there should be
a way to hook 'to' without using opCast. And most types should prefer
that.

I really just don't see a problem here. If opCast is defined, it's perfectly
safe regardless of what would happen if you tried to cast without opCast being
defined. It's also the language-defined way to do type conversions. And I
really
don't see any need to use anything else to make std.conv.to work. By using
opCast, there's a standard way to define type conversion, and there's a
standard way for it to hook into std.conv.to, which seems way better to me
than trying to support every which way that a particular programmer wants to
try and define a conversion function. Clearly, you think that using opCast is
a
problem, but I just don't agree. It's safe; it's standard; and it works.

struct S {
ubyte[] p;
}
import std.stdio;
void main() {
immutable a = S(null);
// ...
auto b = cast(S)a;
writeln(typeof(a.p).stringof);
writeln(typeof(b.p).stringof);
}
which can easily happen in generic code. And you can't tell w/o looking
at the S implementation whether it's safe or not (with an appropriate
opCast it could be).
'cast()' is special, so you can't even use a "safer" opCast like
auto opCast(DT)() inout { return inout(DT)(this.tupleof); }
// or, more likely, returning some templated object instance
because a different return type is not accepted. Using a method
auto b = a.opCast!S();
avoids these problems -- you only have to provide a correct a.opCast
implementation and the compiler will then catch caller mistakes.
'cast' /is/ dangerous, separating the safe operations from the
potentially unsafe ones is desirable. Most of the bugs caused by
mistakenly dropping 'const' etc wouldn't have happened if the explicit
casts weren't there - because the compiler would have complained,
forcing the coder to consider if the conversion really is necessary
and how to handle it properly.
artur

std.conv.to is the standard way to convert one type to
another. I see no
reason to introduce stuff specific to core.time or
std.datetime to do
conversions. It should just hook into the standard stuff for
that. If
everything uses std.conv.to for coverting between types, then
you don't have
to worry about figuring out how a particular programmer
decided that their API
should do it - be it with casts or asOtherType toOtherType or
whatever.
std.conv.to is specifically designed so that any type can hook
their own
conversions into it, and then you can just always use
std.conv.to for
converting types.

But the one doing the work is core.time. In essence, you have
locked away part of the API behind cast, and in order to get it
out without using cast, you have to import another module.
opCast is just a function, it could easily be called opTo, or
simply to(T)().

Now that we have the UFCS, std.conv.to should simply be
implemented as:
T to(F, T)(F from)
{
T t;
from.convert(t);
return t;
}
Then, std.conv should provide convert() functions for built-in
types, e.g.
void convert(wstring from, ref string to);
void convert(long from, ref int to);
etc.
User-defined types could define their own convert method:
struct MyType
{
void convert(ref MyOtherType tgt);
}
This is both safer and more flexible than using opCast(), since
1. you avoid the unfortunate association with the cast operator,
2. convert() can be virtual if desired,
3. convert() can be called directly to modify the target
variable in-place.
Lars

std.conv.to is the standard way to convert one type to another. I see
no
reason to introduce stuff specific to core.time or std.datetime to do
conversions. It should just hook into the standard stuff for that. If
everything uses std.conv.to for coverting between types, then you
don't have
to worry about figuring out how a particular programmer decided that
their API
should do it - be it with casts or asOtherType toOtherType or whatever.
std.conv.to is specifically designed so that any type can hook their
own
conversions into it, and then you can just always use std.conv.to for
converting types.

But the one doing the work is core.time. In essence, you have locked
away part of the API behind cast, and in order to get it out without
using cast, you have to import another module.
opCast is just a function, it could easily be called opTo, or simply
to(T)().

Now that we have the UFCS, std.conv.to should simply be implemented as:
T to(F, T)(F from)
{
T t;
from.convert(t);
return t;
}
Then, std.conv should provide convert() functions for built-in types,
e.g.
void convert(wstring from, ref string to);
void convert(long from, ref int to);
etc.
User-defined types could define their own convert method:
struct MyType
{
void convert(ref MyOtherType tgt);
}
This is both safer and more flexible than using opCast(), since
1. you avoid the unfortunate association with the cast operator,
2. convert() can be virtual if desired,
3. convert() can be called directly to modify the target variable
in-place.

I don't think this is flexible enough. It may not be enough to be able to
fill in another type.
But we don't need to go through ANY of this, just change T opCast(T)() to
T to(T)().
This way, with UFCS, a.to!B() will either call the method if available, or
std.conv.to (assuming it's imported).
-Steve

Because if your unix time is stored in a ulong, for whatever reason, you
may then have to cast to call this function.
time_t implicitly casts to ulong, no matter the system-defined size of
time_t. It's also more future-proof, for dates past 2038.
Is there a specific reason to disallow accepting ulong?
-Steve

Because if your unix time is stored in a ulong, for whatever reason, you
may then have to cast to call this function.
time_t implicitly casts to ulong, no matter the system-defined size of
time_t. It's also more future-proof, for dates past 2038.
Is there a specific reason to disallow accepting ulong?

Because the whole point is that you're operating on time_t, which isn't ulong.
Also, specifically using an unsigned type is wrong, because time_t is signed on
some systems. So, it could be changed to long, but using long instead time_t
will break code on 32-bit systems for SysTime's toUnixTime, and I'd expect
unixTimeToStdTime or unixTimeToSysTime to use the same type as toUnixTime. And
if future-proofing is the issue, then you'll need a 64-bit system anyway,
otherwise the C stuff that you're interacting with wouldn't work correctly with
the larger time_t values.
I can definitely see an argument for just using long and then requiring people
to cast if they're really using time_t and are on a 32-bit system, but that
would break code at this point. I used time_t, because the whole point was
that you were converting to and from time_t.
- Jonathan M Davis

Because if your unix time is stored in a ulong, for whatever reason, you
may then have to cast to call this function.
time_t implicitly casts to ulong, no matter the system-defined size of
time_t. It's also more future-proof, for dates past 2038.
Is there a specific reason to disallow accepting ulong?

Because the whole point is that you're operating on time_t, which isn't
ulong.

No, you are operating on an integer. Unix time is defined as the number
of seconds since 1/1/1970. time_t is what time() returns, there is no
requirement for unix time to ALWAYS be stored as a time_t.

Also, specifically using an unsigned type is wrong, because time_t is
signed on
some systems. So, it could be changed to long, but using long instead
time_t
will break code on 32-bit systems for SysTime's toUnixTime

toUnixTime can return time_t, which can implicitly cast to long or ulong.
It's the *from* unix time that is a problem.
I'd argue that we probably should make it return long or ulong (I think I
did that in Tango's time code), but that is something that can be done
later.

and I'd expect
unixTimeToStdTime or unixTimeToSysTime to use the same type as
toUnixTime.

It should accept the type that has the most utility -- long or ulong. As
long as it accepts time_t without any loss, it does not harm anything.

And
if future-proofing is the issue, then you'll need a 64-bit system anyway,
otherwise the C stuff that you're interacting with wouldn't work
correctly with
the larger time_t values.

What C stuff am I interacting with? Unix Time <=> SysTime conversions are
purely D code.
It won't be very long until Unix will have to tackle this (hopefully they
don't wait until 2037). The most likely scenario is they just increase
the bits for time_t to 64. D will be more ready for that with a change to
long/ulong for unixTimeToSysTime.
-Steve

And
if future-proofing is the issue, then you'll need a 64-bit
system anyway,
otherwise the C stuff that you're interacting with wouldn't
work correctly with
the larger time_t values.

What C stuff am I interacting with? Unix Time <=> SysTime
conversions are purely D code.
It won't be very long until Unix will have to tackle this
(hopefully they don't wait until 2037). The most likely
scenario is they just increase the bits for time_t to 64. D
will be more ready for that with a change to long/ulong for
unixTimeToSysTime.

Or they may treat it heuristically. C standard says that time_t
is implementation defined, so if you want to know which time it
represents, you should use gmtime function which converts it to
broken down form - year, month etc.

If you're using pure D code, then why are you using unix time at all? I would
expect the need for unix time in pure D code to be extremely rare. Pretty much
the whole reason that unix time is supported in the first place is because
that's what C uses. If we'd never had to worry about C, then there shouldn't
have been much need for it.

It won't be very long until Unix will have to tackle this (hopefully they
don't wait until 2037). The most likely scenario is they just increase
the bits for time_t to 64.

As I understand it, that _is_ the solution. Most everything is moving to 64-
bit anyway.

D will be more ready for that with a change to
long/ulong for unixTimeToSysTime.

We're ready for that already with time_t. If time_t isn't 64-bit on your
system when 2038 comes, you're screwed anyway as long as you're dealing with
time_t.
I'm not completely against changing what unixTimeToSysTime takes, but unix
time is very much tied to time_t, and I really don't buy that it buys us much
to make it take a long.
- Jonathan M Davis

Heh, sweet UFCS! Funny thing about this is my editor thinks "seconds"
is a fractional part and is syntax-highlighting it as a floating-point
number. Oops! :p

3. I HATE "safe" cast conversions. If you want to make a conversion, use
a method/property. I don't even know why D allows overloading casting.
Casts are way too blunt for this.

Totally agreed. Because casts can be both defined and a blunt tool you
can never be sure you're actually calling a conversion method. It
could even be removed in a library between version releases,
potentially turning your casts unsafe.

Another example, I once had to convert a long type which
represented
Unix time into DateTime. Here's the code to do it:
return
cast(DateTime)SysTime(unixTimeToStdTime(cast(int)d.when.time));

I have three comments here:
1. unixTimeToStdTime should take ulong.
2. There should be a shortcut for this.
Note on Windows, given a SYSTEMTIME we can do:
return cast(DateTime)SYSTEMTIMEToSysTime(t);
We need an equivalent unixTimeToSysTime, and in fact, I think
we can get rid of unixTimeToStdTime, what is the point of that?
3. I HATE "safe" cast conversions. If you want to make a
conversion, use a method/property. I don't even know why D
allows overloading casting. Casts are way too blunt for this.
The code should be:
return unixTimeToSysTime(d.when.time).asDateTime;

I think anything-to-anything conversion is possible.
T toTime(F,T)(F fromValue, TimeZone zone=Utc)
{
// first make an intermediate value
// the source type needs to support toSysTime method
// also it should not be a primitive type
SysTime temp = fromValue.toSysTime(zone);
// also define an extension method for SysTime
// for conversion to this time type
return temp.toTime!(T);
}
unix time will need a wrapper for strong typing
struct UnixTime { int value; }
SysTime toSysTime(UnixTime fromValue, TimeZone zone=Utc)
{ return SysTime(unixTimeToStdTime(fromValue.value), zone); }
so conversion is
return UnixTime(d.when.time).toTime!DateTime;
may be it can be reduced to
return to!DateTime(UnixTime(d.when.time));

unix time will need a wrapper for strong typing
struct UnixTime { int value; }
SysTime toSysTime(UnixTime fromValue, TimeZone zone=Utc)
{ return SysTime(unixTimeToStdTime(fromValue.value), zone); }
so conversion is
return UnixTime(d.when.time).toTime!DateTime;
may be it can be reduced to
return to!DateTime(UnixTime(d.when.time));

For primitive types one may have functions which convert directly
to SysTime instead of wrappers, this will also enable overloading
if someone uses wider types with the same semantics.
SysTime unixTime(time_t fromValue, TimeZone zone=Utc)
{ return SysTime(unixTimeToStdTime(fromValue), zone); }
return unixTime(d.when.time).toTime!DateTime;
strongly typed time formats with defined conversion method to and
from SysTime will be just
FILETIME ft;
return ft.toTime!DateTime;

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

The issue is when you think you are invoking the opCast operator, but you
inadvertently end up casting using the compiler's type-bypassing version.
I agree the opCast call is safe, it's that its name coincides with the
"throw all typechecks away" operator.

I'd have to experiment to see exactly what is and isn't accepted, but in
my
experience, the compiler rarely allows casting to or from structs without
opCast (the same with classes except for the inheritance tree). So, I
really don't think that there's much risk of accidentally using a cast on
a user- defined type and have it use the built-in cast operator.

I don't think to should ignore opCast, or not use it, but there should be
a way to hook 'to' without using opCast. And most types should prefer
that.

I really just don't see a problem here. If opCast is defined, it's
perfectly safe regardless of what would happen if you tried to cast
without opCast being defined. It's also the language-defined way to do
type conversions. And I really don't see any need to use anything else to
make std.conv.to work. By using opCast, there's a standard way to define
type conversion, and there's a standard way for it to hook into
std.conv.to, which seems way better to me than trying to support every
which way that a particular programmer wants to try and define a
conversion function. Clearly, you think that using opCast is a problem,
but I just don't agree. It's safe; it's standard; and it works.

You're casting away immutable. You pretty much have to assume that that's
unsafe and should never do it unless you know exactly what's going on with the
types involved. And no opCast is involved here, so I don't see how it's even
relevant to the discussion.

'cast()' is special, so you can't even use a "safer" opCast like
auto opCast(DT)() inout { return inout(DT)(this.tupleof); }
// or, more likely, returning some templated object instance
because a different return type is not accepted. Using a method
auto b = a.opCast!S();
avoids these problems -- you only have to provide a correct a.opCast
implementation and the compiler will then catch caller mistakes.
'cast' /is/ dangerous, separating the safe operations from the
potentially unsafe ones is desirable. Most of the bugs caused by
mistakenly dropping 'const' etc wouldn't have happened if the explicit
casts weren't there - because the compiler would have complained,
forcing the coder to consider if the conversion really is necessary
and how to handle it properly.

The issue is when you think you are invoking the opCast operator, but you
inadvertently end up casting using the compiler's type-bypassing version.
I agree the opCast call is safe, it's that its name coincides with the
"throw all typechecks away" operator.

I'd have to experiment to see exactly what is and isn't accepted, but in
my
experience, the compiler rarely allows casting to or from structs without
opCast (the same with classes except for the inheritance tree). So, I
really don't think that there's much risk of accidentally using a cast on
a user- defined type and have it use the built-in cast operator.

I don't think to should ignore opCast, or not use it, but there should be
a way to hook 'to' without using opCast. And most types should prefer
that.

I really just don't see a problem here. If opCast is defined, it's
perfectly safe regardless of what would happen if you tried to cast
without opCast being defined. It's also the language-defined way to do
type conversions. And I really don't see any need to use anything else to
make std.conv.to work. By using opCast, there's a standard way to define
type conversion, and there's a standard way for it to hook into
std.conv.to, which seems way better to me than trying to support every
which way that a particular programmer wants to try and define a
conversion function. Clearly, you think that using opCast is a problem,
but I just don't agree. It's safe; it's standard; and it works.

You're casting away immutable. You pretty much have to assume that that's
unsafe and should never do it unless you know exactly what's going on with the
types involved. And no opCast is involved here, so I don't see how it's even
relevant to the discussion.

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.

Built-in casts are dangerous. opCast is a completely different ballgame.

Exactly - this is the whole point. Overloading safe conversions and
raw explicit casts introduces a kind of bugs, which wouldn't be there
otherwise.
artur

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.
- Jonathan M Davis

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