len is a Go predefined identifier and also a Go built-in predefined function, through the go doc tool we can check the doc of the len function as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$go doc builtin.len
package builtin // import "builtin"

func len(v Type) int
    The len built-in function returns the length of v, according to its type:

        Array: the number of elements in v.
        Pointer to array: the number of elements in *v (even if v is nil).
        Slice, or map: the number of elements in v; if v is nil, len(v) is zero.
        String: the number of bytes in v.
        Channel: the number of elements queued (unread) in the channel buffer;
                 if v is nil, len(v) is zero.

    For some arguments, such as a string literal or a simple array expression,
    the result can be a constant. See the Go language specification's "Length
    and capacity" section for details.

The len function is not new even to Go beginners, as it is a frequently used function in everyday Go development. len’s arguments are mainly variables of composite data types, such as arrays (including pointer types for executing arrays), slices, strings, channels, etc. The returned result is the length of these composite data variables, which is a int type value. I won’t go into too much detail, and you’re probably familiar with it. What I will say is that there is one thing about the len function that you may not be familiar with or care about, and that is when the result of the len(s) expression is a constant and when the result is a variable. Don’t overlook this detail, as it will probably make your program output results you didn’t expect, and I’ll give you an example below.

This example comes from a go quiz tweeted by the author of Go 101, whose original question looked like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

func f() int { return 1 }

var x = [8]int{f()}

var p byte = ( 1 << len([8]int{f()}) ) / 2
var q byte = ( 1 << len(x) ) / 2

func main() {
  println(p, q)
}

Ask what the output of the above example is!

Let’s give you five minutes to think about it! … …

Okay, thinking time is over! Presumably you’ve already run this quiz in the Go playground or the Go compiler and got the correct answer: 0 128 .

Whether you derived it yourself or got it by running the source code, now you tell me why the above quiz outputs 0 128!

I guess many people are as confused as I was at first!

  • Isn’t the return value of len an int? Why is the assignment to p or q of type byte not reported as a compile error?
  • Why is p 0 and q 128? … …

Here is my analysis, for your reference, to see if it can answer your questions.

First of all, the only difference between p and q is that the expression on the right side of the q variable declaration directly uses the length of the array x: len(x), while the argument of the len function in the expression on the right side of the p variable declaration is [8]int{f()}, which is a temporary array and contains an element assignment operation with a function call. Obviously this difference determines the final result.

For questions about the details of the Go language, the official Go language spec is the most authoritative reference . Open the Go language spec and locate the section Length and capacity, where we see the following description of the result of evaluating the len(s) expression.

1
The expression len(s) is constant if s is a string constant. The expressions len(s) and cap(s) are constants if the type of s is an array or pointer to an array and the expression s does not contain channel receives or (non-constant) function calls; in this case s is not evaluated. Otherwise, invocations of lenand cap are not constant and s is evaluated.

The general meaning of this passage is that, for the expression len(s)

  • if s is a string constant, then the expression len(s) is also a constant (constant).
  • If s is an array or a pointer to an array and the expression s does not contain a channel receive call or a non-constant (non-constant) function call , then the len(s) expression is a constant, in which case we do not need to evaluate s.
  • In the rest of the cases, the result of len(s) is not a constant, and we need to evaluate s.

How do we understand the “non-constant function call” in the second article? The Go spec gives an example.

1
2
3
4
5
var z complex128
const (
    c4 = len([10]float64{imag(2i)})  // imag(2i) is a constant and no function call is issued
    c5 = len([10]float64{imag(z)})   // invalid: imag(z) is a (non-constant) function call
)

In the examples, the right-hand side of both c4 and c5 statements are len function calls to arrays. s in len(s) in c4 is [10]float64{imag(2i)}, which is an array that contains an imag function call, but since the argument to the imag is a constant, the call to the imag returns a constant as well, not a single non-constant function call, so the len expression on the right-hand side of the c4 declaration statement is essentially a constant.

The s in len(s) in c5 is [10]float64{imag(z)}, which is also an array, and also contains an imag function call, the difference is that the argument to the imag is a variable of type complex128, so the imag function call is a non-constant function call, and the return value cannot be used as a So for such s, the value of the len expression will not be a constant either, so the value of the len expression cannot be used as the initial value of the constant c5.

With the above knowledge in mind, let’s look at the variables p and q in the quiz above. let’s look at the variable q first.

1
2
var x = [8]int{f()}
var q byte = ( 1 << len(x) ) / 2

We see that the argument to the len function in the initial expression to the right of the variable q is the array x, and that the expression (x) does not contain any function calls, which satisfies the condition that len(s) is a constant, and so len(x) is a constant, which means that the declaration statement of q above is equivalent to the following statement.

1
var q byte = ( 1 << 8 ) / 2

The right side of the equals sign of the equals statement is an untyped integer literal value constant, this constant value is calculated at compile time , the value is 128, the variable q of type byte can store the next 128 this value, so q is equal to 128.

Let’s go back to the variable p.

1
var p byte = ( 1 << len([8]int{f()}) ) / 2

Our len expression here contains a function call to f(). Is f() a non-constant function call? Test it with the following code to find out.

1
2
3
4
5
6
func f() int { return 1 }

const (
    b byte = imag(2i) // ok
    c byte = f() // 编译器错误:f() (value of type int) is not constant
)

We see that f() is not a constant function call like an imag, so the len(s) in the p variable declaration is not a constant. Then that len(s) expression requires an expression evaluation process when assigning a value to the variable p.

So how does the expression ( 1 « len([8]int{f()}) ) / 2 look like? This is a left shift operator («), and the operand to the left of this operator is the untyped constant 1. Is it the default type int of the untyped constant or the variable type byte to the left of the equal sign?

We have to resort to the go spec again. The go spec has a paragraph on left-shift/right-shift expressions reads as follows.

1
The right operand in a shift expression must have integer type or be an untyped constant representable by a value of type uint. If the left operand of a non-constant shift expression is an untyped constant, it is first implicitly converted to the type it would assume if the shift expression were replaced by its left operand alone.

The general idea is that the right operand of a shift expression must be an integer type or an untyped constant that can be represented by a value of type uint. If the left operand of a non-constant shift expression is an untyped constant, it is first implicitly converted to a type, what type? It is the type of the left operand after replacing the entire shift expression with the left operand.

This last sentence is too convoluted, let’s give an example to illustrate, the following example from go spec.

1
2
var s uint = 33
var j int32 = 1<<s

The right-hand side of the declaration statement for variable j is a shift expression, and the shift expression is a non-constant shift expression, where the type of the untyped constant 1 is determined how? According to the above statement, the type of 1 in this expression is equivalent to the type of 1 in the following statement.

1
var j int32 = 1  // 用shift表达式的左操作数(1)替换整个shift表达式(1 << s)后

We see that 1 is an untyped constant, and its final type depends on the type of the variable on the left side of the statement, so 1 is of type int32, and eventually 1<<s is of type int32 (go spec: Arithmetic operators apply to numeric values and yield a result of the same type as the first operand).

Okay, let’s go back to the shift expression in variable p. This expression is also not a constant shift expression. Thus the type of 1 in it is equivalent to the type of 1 in the following statement.

1
var p byte = 1

That is, 1, an untyped constant, is of type byte, so 1 « len([8]int{f()}) overflows after shifting 8 bits to the left, and the result is 0. So the right-hand expression of the variable p evaluates to 0, and the p value is 0.

The Go language is known for its simplicity, but there are not a lot of syntactic details in Go. This go quiz topic is a great test of your ability to grasp the details of the Go language syntax!