-
-
Notifications
You must be signed in to change notification settings - Fork 416
Fix for 12837: Duration's get and individual unit getters are bug-prone. #825
Conversation
I agree with this. I've made this mistake before. |
If it's a property, maybe just Oh, and I completely agree that the current API is way too bug-prone. |
That would work. Also, on the newsgroup, Sonke was proposing
Well, as with many words, partial could mean a lot of things. I don't think that I've heard that one before (though if it's related to string instruments, I don't know that I would have, so I don't play one of those). |
Though actually, now that I think of it, |
I don't really like |
Should weigh in, since I started the controversy from the last pull :)
I do not like Regarding the individual named functions, I would like to see a rename rather than discard. Note that this whole scheme is somewhat flawed -- it's quite often that you need not just the e.g. microseconds less than one millisecond, but perhaps the microseconds less than one second (e.g. As it stands now, to build a foo(Duration d)
{
timeval ts;
ts.tv_secs = d.total!"seconds";
ts.tv_usecs = d.partial!"msecs" * 1000 + d.partial!"usecs";
...
} I think we can use a single function to do this. That is clearly an enhancement, but the reason I bring it up is because it may replace this entire concept. If we're deprecating, we should do it once. Just copying the example I had from the old pull request: d.split!("seconds", "usecs")(&ts.tv_secs, &ts.tv_usecs) |
Oh, and these DEFINITELY are properties. This is almost the canonical example of why you would have a read-only property. |
I'd really much rather just get rid of them rather than have the function proliferation, especially if we're considering adding a function which splits it into all of the individual components.
Agreed. It definitely makes sense to add a function which splits it into all of the components (or a requested subset of them). However, I don't know that that makes it so that it doesn't make sense to have a function for getting a single component of the duration. Maybe it does though. I'll have to think about it. It would obviate the need for coming up with a better name though. :)
Well, that depends on the name. If it were |
Ok, thought about this some more and I think my preference is now.
In any case, 👍 to all three contenders. I think |
Okay. |
tin.tv_sec = cast(typeof(tin.tv_sec)) val.total!"seconds"; | ||
tin.tv_nsec = cast(typeof(tin.tv_nsec)) val.fracSec.nsecs; | ||
} | ||
val.split!("seconds", "nsecs")(&tin.tv_sec, &tin.tv_nsec); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can rewrite this whole thing to be more efficient/straightforward:
ulong secs;
val.split!("seconds", "nsecs")(&secs, &tin.tv_nsec);
tin.tv_sec = secs > tin.tv_sec.max ? tin.tv_sec.max : cast(typeof(tin.tv_sec))secs;
Wow, this is awesome! This is better than I imagined for split. I especially like how you can choose based on the parameters to use the return value instead of giving targets. Excellent work. I would ask for unit tests enforcing invalid calls to split, other than that LGTM. Edit: LGTM is for the code, but have some nits on the docs and other things (as I posted). Possible bad calls: // should be invalid, because you requested a split to nsecs, but did
// not give a target
split!("seconds", "nsecs")(&secs);
// I think this should be invalid, because the code to support it is convoluted.
split!("seconds", "seconds")(&secs, &secs2);
split!("seconds", "seconds")();
// no reason to support this, it's trivial to rewrite the units in order,
// and rejecting this would cut down on template bloat
split!("seconds", "nsecs", "minutes")();
split!("seconds", "nsecs", "minutes")(&secs, &nsecs, &mins);
// possibly this should be bad to cut down on template bloat,
// but I'm not sure.
split!("seconds")(&secs); // error, use total!"seconds"
split!("seconds")(); // same error |
providing the values for the requested units), so if only one unit is | ||
given, the result is equivalent to $(LREF total). | ||
|
||
$(D "nsecs") is accepted by split, but $(D "years") and $(D months) are |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
months
has no quotes, but the others do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Will fix.
I see the |
corresponding units. A pointer to any integral type may be used, but no | ||
attempt is made to prevent integer overflow, so don't use small integral | ||
types in circumstances where the values for those units aren't likely | ||
to fit in an integral type that small. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we enforce this requirement? In other words, you can figure out how big of an integral type you need, and check the target, rejecting at compile time. Not strictly necessary, but would be a nice feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, but I'm willing to bet that some common uses cases then wouldn't work - e.g. when setting timeval
's individual parts, the seconds portion would then have to be a long
when on a 32-bit system, it would be int
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e.g. when setting timeval's individual parts, the seconds portion would then have to be a long when on a 32-bit system, it would be int.
Good point, I agree with that. I suppose we could only do the requirement for the subsequent fractions, not the total one.
My initial reaction was to make
This is invalid but not actually tested for in the unit tests.
Also invalid but not tested for in the unit tests.
Also invalid but not tested for in the unit tests.
This however is considered perfectly valid and is called out in the docs as being valid (mostly to make it clearer how |
} | ||
} | ||
|
||
|
||
/++ | ||
$(RED Deprecated. Please use $(LREF split) instead. Too frequently, | ||
get or one of the individual unit getters is used when the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍. It's way more powerful and more clear now. Good work. |
Okay. I think that I fixed everything that's been mentioned thus far, though I suppose that I should bring up what monarch_dodra complained about in the newsgroup with regards to Personally, I'm divided, because I prefer the pointers, since it's then nice and explicit at the call site, but that does make it so that any code that calls |
I responded in the newsgroup, I think the point of allowing safe code is valid. 2 things:
Hopefully @monarchdodra has some comments here. |
One other point, using pointers, you could specify some parts you are not interested in. For example, if you wanted the number of microseconds less than 1 second, the following might be considered valid: d.split!("seconds", "usecs")(null, &usecs); |
Sure, assuming that
Yeah, but that would then require checking for |
Good point, it's just easier syntax-wise at the call site to pass in null rather than create a dummy variable. You are right though, it makes the generic case less performant. |
I'd point out that correctly implemented scope variables would help here. One thing to note, it should be a pure function, which should guarantee that the pointers never escape. Would that change the allowance of taking addresses of the stack variables? |
I'd greatly prefer it just use Regarding null arguments, another idea (perhaps too convoluted) is to support |
Experience has shown that most people (at least some of who don't read the documentation) seem to expect that get and the indivdual getters return what total returns, causing bugs. So, this commit deprecates Duration.get and all of the individual unit getters (including Duration.fracSec). split has been added in their stead. It takes a list of time unit strings and splits the Duration into those units either assigning them to the provided pointers to integers or by returning a struct with the values for the units as its members. It's likely that most code that will have to change because of these changes is broken anyway, so this will catch bugs.
Okay, it's been updated to use |
LGTM |
Ready to go? |
Also LGTM. |
I think so. Certainly, there appear to be no more objections. |
Auto-merge toggled on |
Fix for 12837: Duration's get and individual unit getters are bug-prone.
Can you add examples on how to translate code to the changelog please :). |
An interesting objection came up. I think we still need to deprecate, but the message needs to be changed. See this discussion in d.learn: http://forum.dlang.org/post/gbaocvqjlwaforacnrcg@forum.dlang.org |
Question: Why have the old functions been deprecated in the same release as the introduction of the new functions? There is no grace period unless you disable deprecation messages, so you can't write code that compiles both under 2.065 and 2.066 unless you use |
Isn't that why deprecations were changed to non-fatal by default? |
Yeah. I don't see a lot of point in not immediately deprecating in most cases now that deprecations just print messages rather than prevent compilation. You get a message telling you that you're going to need to change your code, and you either choose to change it then or wait, but your code continues to compile (and if you don't want to change the code yet or see the messages, you can stil use |
Experience has shown that most people (at least some of who don't read
the documentation) seem to expect that get and the indivdual getters
return what total returns, causing bugs. So, this commit adds partial to
replace get (leaving get as deprecated) and deprecates the individual
unit getters, forcing folks to use total or partial explicitly. It's
likely that most code that will have to change because of these changes
is broken anyway, so this will catch bugs.
Okay. Here's another go at it. Based on the discussion in the previous pull, I renamed
getOnly
topartial
, since while it's a bit obtuse, it goes well withtotal
, and it's shorter thangetComponent
, which gets a bit long when you have to add the template argument to it. I also adjusted the deprecation messages based on David's comments.I feel a bit funny about
partial
being a property, since it takes a template argument, buttotal
has been that way for some time now, and their names are property names, not function names. But we could change them both to be non-property functions without breaking code (it just would make their names a bit odd, since they're nouns rather than verbs).Regardless, I think that these changes (or something very close to them) need to be made, since the current situation with the getters on
Duration
has turned out to be overly error-prone. Fortunately, it's likely the case that most code which needs to convert aDuration
to units wantstotal
, so while some code is bound to have to be changed for this, it's probably either relatively little code, or it's mostly code that should have been usingtotal
anyway.Once these changes have been approved and merged, I'll create a second pull request for std.datetime to fix the one function that will get deprecation warnings thanks to this.