Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I've been using Go for a performance-critical part of a production system and I've been thoroughly enjoying it. But certain things have been odd and frustrating.

One trait that I find frustrating is how pedantic Go is about types.

For example, if I have a float x and an int y, I can't write x / y, I have to write x / float64(y)†. The intent is to force awareness of type conversions that introduce subtle gotchas, but I don't see how it applies to the case float / int.

A better example of the same phenomenon is that alias-style types need to be explicitly cast to their aliased types, which introduces pointless noise into what would otherwise be a nice way of 'marking up' the semantic role of variables and arguments (think type index uint).

†Notice there is a common-or-garden 'int' but no common-or-garden 'float', apparently because one should always be aware of precision. Unfortunately, because of the alias type issue I mention above, 'type float float64' doesn't help.



> but I don't see how it applies to the case float / int

Does the int get converted to a float for this calculation? is sizeof(int) == sizeof(float)? Then you can lose accuracy in the conversion; MAX_INT is larger than the largest same-sized float that can be represented without rounding error.


Fair point, although for the garden-variety int (int32) and float64, this isn't the case. In an ideal world, the compiler would be smart enough to know this, and complain appropriately.


So, you're saying that you want a table that looks like this:

        Type 1   | Type 2 | Allowed?
        --------------------------
        float32  | int32  | n
        float32  | int64  | n
        float64  | int32  | y
        float64  | int64  | n
        etc...
Seems a bit odd to have the majority of conversions banned, but allow some random-seeming exceptions that are safe.

Since implicit conversions are almost always either lossy or dangerous (think 'Fahrenheit(32) - Celsius(64)'), I think it makes sense to remove implicit promotions entirely.


It doesn't seem odd to me. In fact I think it might be a Good Thing.

Having the compiler take care of the safe cases for one makes one less likely to always blindly cast things and leave oneself open to the unsafe cases. For certain values of 'one', of course :)

As for your point about Fahrenheit, the real solution is

  type DegreesF float64;
  type DegreesC float64;
so that the units cannot be accidentally mixed.

Note to self: It might not be a bad idea to have alias-style types deliberately not participate in automatic numeric conversions, although I think it is a mistake they don't currently always auto-convert to their own aliased type.


Or just write your code so you don't need to convert types. I think that's entirely doable, with the possible exception of API boundaries, and removes the need to have complicated and confusing rules about allowed conversions.

And yes, as far as different unit types, you just pointed out exactly what I was getting at. It's relatively rare that you want implicit type conversions.


Maybe so, maybe so. I do agree that numeric implicit conversion is not as simple as it first seemed to me, and would have knock-on effects elsewhere.

Edit: For the record, here are the rules we've discovered so far:

1. Numeric casts: A -> B happens automatically if every value of A can be represented exactly in B. Both must be base types.

2. Aliased casts: A -> B happens automatically if A is an alias of B.

3. Automatic casts only happen if a single automatic cast is required, not more. In other words, x + y should not cause x and y to both be cast to a common type.

Is that it? That doesn't seem to bad actually.


"garden-variety" int wont be int32 forever.


Indeed, in fact that's why rune has been added to the language little more than 6 months ago. Now that rune is used instead of int for representing unicode code points, int can be extended to 64 bit.


That's true, and it will probably change fairly soon, but there are a whole range of safe integer to float casts that will still exist even if int changes from int32 to int64.

And these safe casts should be transparent so that people who regularly do things with floats don't feel like second class citizens in the language.


The check cannot be made at compile time, only at run time, and even if it were possible to make the check at compile time it would still be a bad idea. At some point in the program's 20 year old life time, someone will decide that a number N used somewhere in the program has to become N+1, and N+1 doesn't work. It would be unacceptable for such a trivial change to break the program's compilation, and for a number to carry so much hidden state and meaning.


I don't understand why people are so afraid of compile time errors. They're great! They help us write code and reason about it.

If someone changes some ossified piece of code, and that has the effect that it makes an automatic cast semantically dangerous, I'd be extremely grateful if the compiler scolded me with a helpful error message.


I'm not sure what you mean by "The check cannot be made at compile time, only at run time". Are you talking about a language other than Go? Go is statically typed.

edit: I've been talking about compile time errors that prevent potentially dangerous implicit casts from happening. I think you're talking about something else.


If int64 is the default type for int, then any int will be able to contain values not representable as a float64 (you'd need a float80 or float128 to make it work). To safely convert, a run-time check will be needed.


My understanding of this is the following: having implicit casts between certain fundamental types has always been a source of error that even led to own classes of security issues. Instead, Go, like with other things, is going the explicit way: instead of documenting a list of implicit conversion rules somewhere, the language forces the programmer to think about the conversion, and to explicitly state the intention in the form of an explicit conversion between two data types.

And to be honest, I like code that is explicit in what it does. That makes things easier to read and comprehend.


The intent is to force awareness of type conversions that introduce subtle gotchas, but I don't see how it applies to the case float / int.

Keep at it long enough, and you will.


As the other comment points out, whether there exists a gotcha depends on the sizes, byte -> float32, int32 -> float64, etc are fine, int64 -> float64 is not. There is no reason for the compiler to be ignorant of this.

Moreover, if float means float64 (as I'd suggest be the case) and int continues to mean int32 (which it won't forever), then a lot of common cases would not require explicit casting and be safe.


As the other comment points out, whether there exists a gotcha depends on the sizes, byte -> float32, int32 -> float64, etc are fine, int64 -> float64 is not. There is no reason for the compiler to be ignorant of this.

Yes, but too much variance in how the compiler reacts depending on local implementation details can be quite messy. The principle of least surprise would recommend leaving the feature out, especially since Go dispenses with compiler warnings. Otherwise. we could have the situation where something compiles just fine on your desktop, then breaks when compiled for a different platform like NaCL. How would the developer know this on the desktop, ahead of time? By leaving the feature out, you get a more informant and reliable tool.


If int means int64 on one platform and int32 on another platform, one should expect that some things might require care to port correctly.

A compile-time error because an automatic cast can't be performed on the new platform is infinitely better than a forced cast that silently introduces run-time errors.


If int means int64 on one platform and int32 on another platform, one should expect that some things might require care to port correctly.

That's just the way things have been. It's not a particularly good or pleasant situation. If the Go team wants to make this better, all the power to them. (And I know for a fact that it doesn't have to be this way. It's just the expectation we've come to accept as normal. Squeak runs bit-identically on over 50 combinations of OS and processor.)

A compile-time error because an automatic cast can't be performed on the new platform is infinitely better than a forced cast that silently introduces run-time errors

"Silently" here being that the programmer mindlessly puts in the cast because the compiler "forces" her to? I don't think the compiler is at fault here.


But remember that constants in Go are untyped, so in my experience this is not such a big issue, you can do for example: f := 3.14; x := f / 2


I think there are int32 values that can't be represented in a float32, e.g. 16,777,217 (and 9,007,199,254,740,993 for int64/float64)


Correct, but that's not true for many elements of {int8,uint8,int16,uint16,int32,uint32} * {float32,float64}


the thing is, if you've marked up the semantic use of a type, it is no longer guaranteed that it meaningfully supports all the operations the "base type" did. in your 'type index uint' example, for instance, you would typically not divide an index by an int.


That's an interesting point.

Although your example argues against round-tripping the conversation rather than the conversion per se. Which I'm not proposing.

In your example, my proposal would have an index get automatically cast to an int, then undergo division, remaining an int. Who knows why you divided it? But you wouldn't get an index back.




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: