Type-safety and time intervals in D and Go

My favourite language is now, by far, D. It’s not just not even close. I’ve also been known
to make my opinion on Go be publicly known as “I really don’t like it.”. My work buddy Jeff
is nuts over Go though, and I try not to hold it against him. We keep arguing for “our”
language and disparaging the other guy’s, and it’s all in good fun.

As part of that banter, he sent me a blog post link on Google Plus about flaky tests and what they
tell you about your code. It’s a good read, and exemplifies some of the real-world
engineering problems that happen when developing software. As I read it though, my eyes
roll a bit when I encounter this bit of code:

var Timeout time.Duration
Timeout = time.Duration(conf("timeout", 100)) * time.Millisecond
cache := memcache.New(Servers...)
cache.Timeout = Timeout * time.Millisecond

The first thing I didn’t like about it is that the multiplication by
time.Millisecond looks like C. It’s not that different from
multiplying by a preprocessor macro, with all that entails. Don’t we
know better now? std::chrono from C++ is ugly, but it’s still better
than this.

Related to the C-ness of it, and much more importantly, the second time the code
multiplies by time.Millisecond is the cause of the bug the blog post is about.
And immediately I think: that shouldn’t compile. Maybe it’s my physicist background,
but multiplying time by a time unit shouldn’t work. Or at least you shouldn’t be
able to pass that value to a function expecting a time unit (instead of time squared).

I immediately wrote some D code to make sure I didn’t embarass myself by stating
that in D that would be a compilation error, and to my joy the following code
didn’t compile:

import std.datetime;
void main() {
    auto time = 2.seconds;
    auto oops = time * 3.seconds;
}
foo.d(5): Error: 'time' is not of arithmetic type, it is a Duration
foo.d(5): Error: 'dur(3L)' is not of arithmetic type, it is a Duration

Multiplying time by a scalar works as expected, though, which is what should happen. I showed him the code and he seemed really interested in it, and also wondered
which design decisions led to the current state of affairs. According to him the Go
team likes type-safety, so it seems odd. He also asked me if it would be a compilation
error in Haskell, to which the answer was obviously, in Barney Stinson style, “please!”.

I have to admit letting out a rather childish “sucks to be you” at Jeff today in
the office and basking in my elevated sense of self-worth and computer language choice.

Go D! Pun half-intended.

Advertisement

6 thoughts on “Type-safety and time intervals in D and Go

  1. jra says:

    Turns out they have already talked about this: https://groups.google.com/forum/#!searchin/golang-nuts/duration/golang-nuts/HWNfZgC8938/ssfEgVX_rU0J

    Unfortunately, though there are interesting insights in that thread, the key question, “why did Go end up with a type system that can’t catch stuff like this?” isn’t answered.

    One insight in that thread is that because time.Duration is implemented as an int64, it inherits the arithmetic rules from ints. If was a struct that contained one field, an int64, then + and * would be undefined on it. Then the Add() and Multiply() methods you’d add might be able to protect you a bit more. What would happen in D if the Duration type was derived from a simple int type? Would it still have the “prevents Duration * Duration” behavior we’re looking for?

    It think the bottom line is that Go’s type system is pragmatic, and to be pragmatic, it is not rigorous like Haskell, Ada, etc. But it does seem like how you pick the underlying type of a new type lets you choose where you want to be on the safe/convenient continuum.

    -jeff

    • atilaneves says:

      A struct with one int64 is a duration “implemented as an int64”, but since it’s a different type different rules apply. If the author’s of std.datetime simply used a typedef/alias for int64, then it’d be just as type-unsafe as the Go example. But nobody would do that in D.

      D’s type system is also pragmatic. It inherits some of the implicit conversions from C.

  2. Jesse Phillips says:

    In Go, time.Millisecond is in fact a scalar. D avoids this mistake by removing the manual conversion to get a duration: conf(“timeout”, 100).msecs would do just fine assuming a conf() function. You’d end up with a duration and Timeout.msecs wouldn’t compile.

    • Jesse Phillips says:

      Nevermind, I can’t seem to read Go docs. time.Millisecond means grab the Duration struct and pull values from it. So it is a Duration * Duration operation.

  3. CC says:

    Multiplying time by time is actually quite trivial in physics when talking about kinematic.

    Acceleration = distance / time * time

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: