Lexical elements: Semicolons

January 11, 2023

Go’s use of semicolons is one area of confusion for newcomers to the language. It certainly was for me. But it’s more intuitive than it seems.

Here’s the formal explanation:

Semicolons

The formal syntax 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

  2. To allow complex statements to occupy a single line, a semicolon may be omitted before a closing ")" or "}".

To reflect idiomatic use, code examples in this document elide semicolons using these rules.

What this means in practice is that Go “officially” uses semicolons, a lot like C/C++, kind of “all over the place.”

This is valid Go, which looks a lot like C:

package main;

import "fmt";

func main() {
	var x = 1;
	if (x > 0) {
		fmt.Println(x);
	};
};

But since the Go tokenization process adds most semicolons for you, based on the rules described, we can simplify this code when writing it, to be a bit less tedious:

package main

import "fmt"

func main() {
	var x = 1
	if x > 0 {
		fmt.Println(x)
	}
}

(This example also removes the unnecessary parentheses around the conditional expression, which we’ll get to later.)

So how do you know when it’s safe to remove a semicolon?

Well, you can memorize the rules above. But in practice, you can mostly just always omit them. As a general rule, the only times you need to explicitly type semicolons is when you want to put multiple statements on a single line. In other words, when you prefer a semicolon over a line break.

By convention, this is done in (small) number of cases, such as separating the if statement optional “simple statement” from the expression block:

if x := 1
x > y {
	return x
}

is more commonly expressed with a semicolon as:

if x := 1; x > y {
	return x
}

Quotes from The Go Programming Language Specification, Version of June 29, 2022

Share this

Related Content

Empty structs

We finally we have enough knowledge for the EBNF format not to seem completely foreign, so let’s jump back and take a look at that, with the examples provided in the spec… Struct types … StructType = "struct" "{" { FieldDecl ";" } "}" . FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] . EmbeddedField = [ "*" ] TypeName [ TypeArgs ] . Tag = string_lit . // An empty struct.

Struct tags

Struct types … A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag. The tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored. struct { x, y float64 "" // an empty tag string is like an absent tag name string "any string is permitted as a tag" _ [4]byte "ceci n'est pas un champ de structure" } // A struct corresponding to a TimeStamp protocol buffer.

Struct method promotion

Yesterday we saw an example of struct field promotion. But methods (which we haven’t really discussed yet) can also be promoted. Struct types … Given a struct type S and a named type T, promoted methods are included in the method set of the struct as follows: If S contains an embedded field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.