try/catch idiom in std.datetime

Andrei Alexandrescu SeeWebsiteForEmail at erdani.org
Sun Nov 17 22:32:46 PST 2013


I'm of a stance that good code should have many throws and few try/catch 
statements. I was curious how Phobos does there, so I ran a few simple 
greps around.

Seems like there's about 145 try statements in Phobos. Of these, more 
than a third (51) belong to std.datetime.

Looks like the following idiom is often present in std.datetime:

     @property FracSec fracSec() const nothrow
     {
         try
         {
             auto hnsecs = removeUnitsFromHNSecs!"days"(adjTime);

             if(hnsecs < 0)
                 hnsecs += convert!("hours", "hnsecs")(24);

             hnsecs = removeUnitsFromHNSecs!"seconds"(hnsecs);

             return FracSec.from!"hnsecs"(cast(int)hnsecs);
         }
         catch(Exception e)
             assert(0, "FracSec.from!\"hnsecs\"() threw.");
     }

(It may seem I chose this example to discuss the "let's insert one empty 
line every other line" idiom. I didn't.) Essentially the code relies on 
calls that may generally throw, but calls them with parameters that 
should guarantee there won't be any throwing. In wanting to offer as 
many "nothrow" guarantees as possible, the code ends up inserting these 
try/catch statements - seemingly needlessly.

This is quite heavy-handed, so I was wondering what we could do to 
improve on this. I thought of the following possibility:

     @property FracSec fracSec() const nothrow
     {
         scope(failure) assert(0, "FracSec.from!\"hnsecs\"() threw.");
         auto hnsecs = removeUnitsFromHNSecs!"days"(adjTime);
         if (hnsecs < 0)
             hnsecs += convert!("hours", "hnsecs")(24);
         hnsecs = removeUnitsFromHNSecs!"seconds"(hnsecs);
         return FracSec.from!"hnsecs"(cast(int)hnsecs);
     }

This at least leaves the normal path unaltered and deals with the 
unexpected in a more isolated manner. I was pleased that the changed 
code compiled just fine. My happiness was short-lived because before 
long I figured this compiles as well:

         ...
         scope(failure) {}
         ...

So it's not that the compiler cleverly figured the function will not 
throw. Instead, the compiler considers everything dandy as soon as it 
sees a scope(failure), no matter of the statement it controls. I'll 
report that bug soon.

What would be the best approach here?

0. Do nothing. This is as good as it gets.

1. Fix scope(failure) and then use it.

2. Relax the nothrow guarantees. After all, nothrow is opt-in.

3. Look into API changes that add nothrow, narrower functions to the 
more general ones that may throw.

4. ...?


Andrei


More information about the Digitalmars-d mailing list