10

At first glance, it seems like one might opt for uint when you need an int that you don't want to be negative. However, in practice it seems that int is nearly always preferred.

I see general recommendations like:

  • "Generally if you are working with integers you should just use the int type."
  • "uint should generally only be used for doing binary operations"
  • "Don't use unsigned types to enforce or suggest that a number must be positive. That's not what they're for."
  • "this is what The Go Programming Language recommends, with the specific example of uints being useful when you want to do bitwise operations"

I also noticed that Go will let you convert a negative int to uint and give some odd results:

    x := -5
    y := uint(x)
    fmt.Println(y)

>> 18446744073709551611

So, my understanding is that I should always use int when dealing with whole numbers, regardless of sign, unless I find myself needing uint, and I'll know it when that's the case (I think???).

My questions:

  • Is this the right takeaway?
  • If so, why is this the case?
  • What's an example of when one should use uint? -- maybe a specific example, as opposed to "when doing binary operations", as I'm not sure I know what that means :)

Also, I'm asking specific to Go's implementation.

AaronKronberg
  • 197
  • 1
  • 7

2 Answers2

6

This answer is for C but it's relevant here.

Generally if you are working with integers you should just use the int type.

This is recommended generallly because most of the code that we "generally" encounter deals with type int. And it's also not generally required for you to choose between the use of an int and a uint type.

Don't use unsigned types to enforce or suggest that a number must be positive. That's not what they're for.

This is quite subjective. You can very well use it to keep your program and data type-safe and needn't be bothered with dealing with the occasional errors that come due to the case of a negative integer.

"this is what The Go Programming Language recommends, with the specific example of uints being useful when you want to do bitwise operations"

This looks vague. Please add the source for this, I would like to read up on it.

x := -5
y := uint(x)
fmt.Println(y)

>> 18446744073709551611

This is typical of a number of languages. Logic behind this is that when you convert an int type to a uint, the binary representation used for the int is kind of shoved into the uint type. In the end, everything is just an abstraction over binary. For example, take a look at this code and it's output:

a := int64(-123)
byteSliceRev := *(*[8]byte)(unsafe.Pointer(&a))      // The byte slice representation we get is LTR in increasing order of significance
u := uint(a)
byteSliceRevU := *(*[8]byte)(unsafe.Pointer(&u))
byteSlice, byteSliceU := make([]byte, 8), make([]byte, 8)
for i := 0; i < 8; i++ {
    byteSlice[i], byteSliceU[i] = byteSliceRev[7-i], byteSliceRevU[7-i]
}
fmt.Println(u)
// 18446744073709551493
fmt.Printf("%b\n", byteSlice)
// [11111111 11111111 11111111 11111111 11111111 11111111 11111111 10000101]
fmt.Printf("%b\n", byteSliceU)
// [11111111 11111111 11111111 11111111 11111111 11111111 11111111 10000101]

The byte representation of both the int64 type of -5 is the same as for uint type of 18446744073709551493.

So, my understanding is that I should always use int when dealing with whole numbers, regardless of sign, unless I find myself needing uint, and I'll know it when that's the case (I think???).

But isn't this more or less true of every code that "we" write.?!

Is this the right takeaway? If so, why is this the case?

I hope I have answered these two questions. Feel free to ask me if you still have any doubts.

What's an example of when one should use uint? -- maybe a specific example, as opposed to "when doing binary operations", as I'm not sure I know what that means :)

Imagine a scenario in which you have a table in your database with a lot of entries with an integer for an id, which is always positive. If you store this data as an int one bit of every entry is effectively useless and when you scale this, you are losing a lot of space when you could have just used a uint and saved it. Similar scenario can be thought of while transmitting data, transmitting tons of integers to be precise. Also, uint has double the range for positive integers compared to their counterpart signed integers due to the extra bit, so it will take you longer to run out of numbers. Storage is cheap now so people generally ignore this supposedly minor gain.

The other usecase is type-safety. A uint can never be negative so if a part of your code is delicate to negative numbers, it can prove to be pretty handy. It's better to get the error before wasting resource on the data just to find out it's impermissible because it's negative.

Vaibhav Mishra
  • 415
  • 3
  • 10
  • Thanks! But my observation is that the idiomatic Go way of doing things is to prefer int. On its surface, if you didn't know that general recommendation, it seems logical to choose the more restrictive uint. Also, I guess I'm specifically thinking about working in memory in Go. I definitely see your point about optimizing for size with uint for db storage or transmitting data. But in memory in Go, it does seem just as easy to use uint, but the general recommendation is to not do so. Why? Just that it's easy to not think about it and use int (when size and max # aren't an issue)? – AaronKronberg Jul 27 '20 at 01:15
  • Or perhaps my premise is wrong? Is it not idiomatic Go to prefer int in the cases I'm describing? On type safety, given how easy it is to shove a negative int into a uint, doesn't feel safe. Perhaps not violating type safety in the strictest sense, but doesn't feel like what I expect from it. Maybe this is a key reason for the recommendation? – AaronKronberg Jul 27 '20 at 01:15
  • Here's a reference that prompted a good bit of my question: https://www.reddit.com/r/golang/comments/8plmo0/use_int_as_the_type_if_a_number_will_never_be/ – AaronKronberg Jul 27 '20 at 01:20
  • 1
    "But my observation is that the idiomatic Go way of doing things is to prefer int. On its surface, if you didn't know that general recommendation, it seems logical to choose the more restrictive uint." This may very well be your observation, but please understand the answer "what idiomatic is?" is quite subjective. You will find people fight tooth and nails over where to put the braces in an `if-else` clause, with their very good reasons, and no one will win since it actually is that subjective. – Vaibhav Mishra Aug 01 '20 at 18:10
  • 1
    The end result we get is a list of rules that makes everyone's life easier, and as it is case with all rules people will disagree and that's ok. It's not about me not knowing of the recommendations, which in itself done by real people and people recommend all sorts of things. It's about what I think is right. `uint` is used throughout the standard library of Go, in packages like `image` most probably for the reasons I've mentioned. And the type-safety that comes with using `uint` is one of the most idiomatic things that Go recommends. It's just, imo, `int` just comes easy and is more natural. – Vaibhav Mishra Aug 01 '20 at 18:15
  • 1
    About storage, before being transmitted or being stored, everything remains in memory usually. In the case of caches too if you are implementing in-memory caches. Many databases also implements in-memory models, but again it's not that big of a deal since memory is cheap. Also, type-casting always have its downisides, but notice how your `uint` has now a positive value. At least now your app won't break. – Vaibhav Mishra Aug 01 '20 at 18:32
  • 1
    I think the more important use-cases are still [this](https://stackoverflow.com/a/16381089/8565339) and the increased available numbers. – Vaibhav Mishra Aug 01 '20 at 18:35
3

Package Image uses uint and so crypto/tls, so when you use these packages you must use uint.

I use it logically at first but I don't fight about it and if it became an issue I use a practical approach.

like why using int for len()

ZAky
  • 1,209
  • 8
  • 22