Line Break Rules In Go

If you have written go code much, you should have known that we can't use arbitrary code styles in Go programming. Specifically speaking, we can't break a code line at an arbitrary character position. The remaining of this article will list the detailed line break rules in Go.

Semicolon Insertion Rules

One rule we often obey in practice is, we should not put the a starting curly brace ({) of the explicit code block of a control flow block on a new line.

For example, the following for loop code block fails to compile.
	for i := 5; i > 0; i--
	{ // unexpected newline, expecting { after for clause
	}
To make it compiles okay, the starting curly brace mustn't be put on a new line, like the following:
	for i := 5; i > 0; i-- {
	}

However, there are some exceptions for the rule mentioned above. For example, the following bare for loop block compiles okay.
	for
	{
		// do something ...
	}

Then, what are the fundamental rules to do line breaks in Go programming? Before answering this question, we should know that the formal Go grammar uses semicolons ; as terminators for statements. However, we seldom use semicolons in our Go code. The reason is most semicolons are optional and can be omitted. Go compilers will insert the omitted semicolons for us automatically in compiling.

For example, the ten semicolons in the following program are all optional.
package main;

import "fmt";

func main() {
	var (
		i   int;
		sum int;
	);
	for i < 6 {
		sum += i;
		i++;
	};
	fmt.Println(sum);
};

Assume the above program is stored in a file named semicolons.go, we can run go fmt semicolons.go to remove all the unnecessary semicolons from that file. Compilers will insert the removed semicolons back (in memory) automatically in compiling the source code.

What are the semicolons insertion rules in Go? Let's read the semicolon rules listed in Go specification.

The formal grammar uses semicolons ";" as terminators in a number of productions. Go programs may omit most of these semicolons using the following two rules:

  1. When the input is broken into tokens, a semicolon is automatically inserted into the token stream immediately after a line's final token if that token is
    • an identifier
    • an integer, floating-point, imaginary, rune, or string literal
    • one of the keywords break, continue, fallthrough, or return
    • one of the operators and punctuation ++, --, ), ], or }
  2. To allow complex statements to occupy a single line, a semicolon may be omitted before a closing ")" or "}".

For the scenarios listed in the first rule, surely, we can also insert the semicolons manually, just like the semicolons in the last code example. In other words, these semocolons are optional.

The second rule means the last semicolon in a multi-item declaration before the closing sign ) and the last semicolon within a code block or a (struct or interface) type declaration before the closing sign } are optional. If the last semicolon is absent, compilers will automatically insert it back.

The second rule lets us be able to write the following valid code.
import (_ "math"; "fmt")
var (a int; b string)
const (M = iota; N)
type (MyInt int; T struct{x bool; y int32})
type I interface{m1(int) int; m2() string}
func f() {print("a"); panic(nil)}
Compilers will automatically insert the omitted senicolons for us, as the following code shows.
var (a int; b string;);
const (M = iota; N;);
type (MyInt int; T struct{x bool; y int32;};);
type I interface{m1(int) int; m2() string;};
func f() {print("a"); panic(nil);};

Compilers will not insert semicolons for any other scenarios. We must insert the simicolons manually as needed for other scenarios. For example, the first semicolon at each line in the last code example are all required. The semicolons in the following example are also required.
var a = 1; var b = true
a++; b = !b
print(a); print(b)

From the two rules, we know that a semicolon will never be inserted just after the for keyword. This is why the bare for loop example shown above is valid.

One consequence of the simicolon insertion rules is that the self increment and self decrement operations must appear as statements. They can't be used as expressions. For example, the following code is invalid.
func f() {
	a : = 0
	println(a++)
	println(a--)
}
The reason why the above code is invalie is for compilers will view it as
func f() {
	a : = 0
	println(a++;)
	println(a--;)
}

Another consequence of the simicolon insertion rules is we must put the dot . signs at the end of code line if we break a method call chain into miltiple lines.
	anObject.
		MethodA().
		MethodB().
		MethodC()
We can't write the above method call chain as
	anObject
		.MethodA()
		.MethodB()
		.MethodC()
Compilers will insert a simicolon at the end of each line in the modified version, so the above code is equivalent to the following code which is invalid obviously.
	anObject;
		.MethodA();
		.MethodB();
		.MethodC();

There are many other inappropriate line break cases. If you think the above line break rules are some complex to grasp, here is simpler rule to follow. The simpler rule is a subset of the full rules.

Generally, we should only break a code line just after a binary operator, an assignment sign, a single dot (.), a comma, a simicolon, or any opening brace ({, [, ().

The semicolon insertion rules make us write cleaner code. They also make it is possible to write some valid but a little weird code. For example,
package main

import "fmt"

func alwaysFalse() bool {return false}

func main() {
	for
	i := 0
	i < 6
	i++ {
		// use i ...
	}
	
	if x := alwaysFalse()
	!x {
		// do something ...
	}
	
	switch alwaysFalse()
	{
	case true: fmt.Println("true")
	case false: fmt.Println("false")
	}
}

All the three control flow blocks are valid. Compilers will insert a semicolon at the end of each of line 9, 10, 15 and 20.

Please note, the switch-case block in the above example will print a true instead of a false. It is different from
	switch alwaysFalse() {
	case true: fmt.Println("true")
	case false: fmt.Println("false")
	}
If you use the go fmt commend to format the former one, a semocolon will be appended automatically after the alwaysFalse() call, so it will become to
	switch alwaysFalse();
	{
	case true: fmt.Println("true")
	case false: fmt.Println("false")
	}
The modified version is equivalent to the following one.
	switch alwaysFalse(); true {
	case true: fmt.Println("true")
	case false: fmt.Println("false")
	}

That is why it will print a true.

It is a good habit to run go fmt, and go vet, often for your code.

For a rare case. the semicolon insertion rules also make some code look valid but invalid actually. For example, the following two code snippets both fail to compile.
func fa() {
	var c chan bool
	select {
	case <-c:
	{
		goto A
		A: // compiles okay
	}
	case <-c:
		goto B
		B: // syntax error: missing statement after label
	case <-c:
		goto C
		C: // compiles okay
	}
}
func fb() {
	switch 3 {
	case 2:
		goto B
		B: // syntax error: missing statement after label
	case 1:
	{
		goto A
		A: // compiles okay
	}
	case 0:
		goto C
		C: // compiles okay
	}
}

The two compilation errors indicate that there must be a statement following a label declaration. But it looks each label in the above two examples aren't followed by a statement. Why are only the two B: label declarations are invalid? The reason is, by the simicolon insertion rules mentioned above, compilers will insert a simicolon at the end of each of the valid label declarations, but not at the ends of the two B: label declarations. Each ; inserted can be viewed as a blank statement, which is why the A: and C: label declarations are all valid.

We can manually insert a simicolon (a blank statement) at the end of each of the two B: label declarations to make them compile okay.

As label declarations are also statements, the following code is valid, though the label declaration form is totally useless in practice.
func f() {
	A:B: // has not any usefulness
	for {
		break B
		goto A
	}
}

Comma (,) Will Not Be Inserted Automatically

In some syntax forms containing multiple alike items, commas are used as seperators, such as composite literals, function argument lists, and parameter lists and result lists in function prototypes. In such a syntax form, the last comma following the last item is optional if the comma is not the last effective character in its respective code line, otherwise, the comma is required to be present. Compilers will not insert commas automatically for any case.

For example, the following code is valid.
func f1(a int, b string,) (x bool, y int,) {
	return true, 789
}
var f2 func (a int, b string) (x bool, y int)
var f3 func (a int, b string, // the last comma is required
) (x bool, y int,             // the last comma is required
)
var _ = []int{2, 3, 5, 7, 9,} // the last comma is optional
var _ = []int{2, 3, 5, 7, 9,  // the last comma is required
}
var _ = []int{2, 3, 5, 7, 9}
var _, _ = f1(123, "Go",) // the last comma is optional
var _, _ = f1(123, "Go",  // the last comma is required
)
var _, _ = f1(123, "Go")
However, the following code is invalid, for compilers will insert a semicolon for each line in the code, except the second line.
func f1(a int, b string,) (x bool, y int // error: unexpected newline
) {
	return true, 789
}
var _ = []int{2, 3, 5, 7, 9 // error: unexpected newline
}
var _, _ = f1(123, "Go" // error: unexpected newline
)

Final Words

Like some other features in Go, there are both praises and criticisms for the semicolon insertion rules. Some programmers don't like the rules, for they think the rules limit the freedom of code styles. Praisers think the rules make code compile faster, and make the code written by different programmers look similar, so that it is easy to understand the code written by each other.

Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies description flaws and code bugs.