Go

What type does an untyped constant take in a non-constant shift? What does this print?

Question 39HardGo 1.22 to 1.25
var s uint = 33

var i = 1 << s          // 1 takes type int
var j int32 = 1 << s    // 1 takes type int32: the bit is shifted out, j == 0
var k = uint64(1 << s)  // 1 takes type uint64
var m int = 1.0 << s    // 1.0 takes type int, so this is legal

// var u = 1.0 << s       // compile error: shifted operand 1.0 (type float64) must be integer
// var v float32 = 1 << s // compile error: shifted operand 1 (type float32) must be integer

fmt.Println(i, j, k, m)

Output (64-bit platforms):

8589934592 0 8589934592 8589934592

This is one of the strangest rules in the spec. When the shift count is not a constant, the left operand, if it is an untyped constant, takes the type it would have if the shift were replaced by the left operand alone. So in var j int32 = 1 << s the constant 1 becomes int32, and shifting an int32 by 33 gives 0. There is no overflow error, because the shift happens at run time. In var u = 1.0 << s the constant would default to float64, and floats can't be shifted, so it doesn't compile. But var m int = 1.0 << s is fine, because the context makes 1.0 an int.

Related rules:

  • With a constant shift count, 1 << 33 is evaluated exactly at compile time, and only the final value must fit its type.
  • Since Go 1.13 the shift count can be a signed integer. A negative count panics at run time. A negative constant count is a compile error.
  • Shifting by the type's width or more is legal and gives 0 (or -1 for right shifts of negative signed values). It is not undefined behavior as in C.
  • A real-world trap: x := float64(1 << n) with a variable n does not compile, because the conversion makes 1 a float64. Write float64(1 << n) as float64(int(1) << n), or use math.Ldexp(1, int(n)). In general, give the constant an explicit type so readers don't need to know this rule.

More on Language Fundamentals & Types

All 39 Language Fundamentals & Types questions