Operators and Punctuation
A complete reference of Go operator and punctuation tokens their classification and their role in program structure
Operators and punctuation form one of the four fundamental token classes in Go, alongside identifiers, keywords, and literals. The lexer transforms raw source characters into these tokens, and the parser uses them to build the syntax tree of every Go program. This document catalogs every operator and punctuation token recognized by the Go language, explains how they are grouped, and highlights the lexical rules that govern their recognition.
The Full Set of Operator and Punctuation Tokens
The Go specification (as of Go 1.18) defines the following character sequences as operators and punctuation:
+ & += &= && == != ( )
- | -= |= || < <= [ ]
* ^ *= ^= <- > >= { }
/ << /= <<= ++ = := , ;
% >> %= >>= -- ! ... . :
&^ &^= ~
These tokens fall into two conceptual buckets: operators, which act on values and produce results, and punctuation, which structures code. The next two sections separate them clearly.
Operators That Perform Computation
Every operator in the following categories is a token that the lexer hands off to the parser. The expressions chapter covers their semantics in depth; here the focus is on what each token represents and how the lexer distinguishes them.
Arithmetic Operators
+, -, *, /, %
Addition, subtraction, multiplication, division, and remainder. The same + token also concatenates strings when both operands are strings. The lexer does not know about types—it only knows that + is a valid token. The parser later decides which operation to apply based on context.
a := 10
b := 3
sum := a + b // 13
mod := a % b // 1
greeting := "Hello, " + "World" // "Hello, World"
When + appears with two string values, the generated token is still +; the compiler’s type checker selects string concatenation instead of integer addition.
Relational Operators
==, !=, <, <=, >, >=
These produce a boolean result after comparing two values. The two-character tokens <= and >= must be read greedily: the lexer consumes the = after < or > to produce a single token. A < immediately followed by something other than = becomes a plain less-than token.
x := 7
y := 10
fmt.Println(x == y) // false
fmt.Println(x <= y) // true
The lexer never inserts whitespace inside these tokens, so < = is always two separate tokens: < and =.
Logical Operators
&& (AND), || (OR), ! (NOT)
These operate exclusively on boolean values. The first two short-circuit evaluation, but that is a runtime behavior—the lexer simply recognizes them as single tokens.
isValid := true
isReady := false
if isValid && isReady {
fmt.Println("Go")
}
// The token && is a logical AND, not a bitwise &.
Single & is not a logical operator:
Writing isValid & isReady compiles only if the variables are integers—for booleans it would be a compile-time error. Beginners often reach for & because it looks similar; always use && for boolean logic.
Bitwise Operators
&, |, ^, &^, <<, >>, ~
These work on integer bits. The &^ token (AND NOT, or bit clear) is unique to Go. The ~ token, added in Go 1.18, acts as the bitwise complement (unary operator) and also appears in generic type constraints to mean “any type whose underlying type is …”. The lexer treats ~ identically in both contexts—the parser assigns meaning.
a := 0b1100_1100
b := 0b1010_1010
fmt.Printf("%08b\n", a&^b) // 01000100 (bits cleared where b has 1)
fmt.Printf("%08b\n", ^a) // 00110011 (bitwise complement)
The left and right shift tokens << and >> are two-character sequences. The lexer reads them as single shift tokens, not as two less-than or greater-than tokens.
Assignment Operators
=, :=, +=, -=, *=, /=, %=, &=, |=, ^=, &^=, <<=, >>=
The simple assignment = stores a value. The compound assignments += and friends combine an operation with assignment. The short variable declaration := declares a new variable and assigns it in one step. These tokens are all recognized atomically—the lexer never splits += into + and =.
var count int
count = 10
count += 5 // token += , equivalent to count = count + 5
name := "Gopher" // token := , declares name and assigns string
Short variable declaration inside functions:
:= is the idiomatic way to introduce new variables within a function body. The lexer sees := as one token, so a colon followed by an equals without the colon being part of a different construct always becomes :=.
Address, Pointer, and Channel Operators
&, *, <-
These tokens change roles based on where they appear. As unary prefix operators, & takes an address (address-of) and * dereferences a pointer. As binary infix operators, & means bitwise AND and * means multiplication. The <- token indicates sending or receiving on a channel.
x := 42
p := &x // & as address-of
fmt.Println(*p) // * as dereference
ch := make(chan int, 1)
ch <- 100 // <- as send
val := <-ch // <- as receive
The lexer does not care about pointer semantics—it only knows that & and * are valid tokens. The parser decides whether they are unary or binary based on the surrounding grammar.
Increment and Decrement
++, --
These tokens increase or decrease a numeric value by one. In Go they are statements, not expressions. You cannot use the result of ++ or -- directly—they must stand alone.
i := 1
i++ // token ++ , statement
// j := i++ // illegal: ++ is not an expression
++ and -- are statements, not expressions:
A line like x := y++ triggers a syntax error because the parser expects an expression after :=, not a statement. Always place ++ or -- on its own line, with the operand on the same line.
Punctuation That Structures Code
These tokens do not produce values; they delimit, group, and separate the elements of a program.
Parentheses, Brackets, and Braces
( ) [ ] { }
- Parentheses enclose parameter lists, group sub-expressions, and wrap type conversions.
- Brackets denote indexing for arrays, slices, and maps.
- Braces delineate block scopes for functions, control structures, and composite literal values.
The lexer emits each as an individual token. Matching is handled by the parser.
Semicolon
; is the formal statement terminator. Go’s lexer inserts semicolons automatically at line breaks under well-defined rules, so you almost never type them. The few places they appear explicitly are within for loop clauses or when placing multiple statements on a single line.
Comma
, separates function arguments, elements in composite literals, and identifiers in variable declarations. It is a lightweight delimiter recognized as a single token.
Period
. provides member access: p.Field or fmt.Println. It is also part of floating-point literals, but there it is absorbed into the literal token, not emitted as a separate punctuation token.
Colon
: appears in several syntactic positions: as part of the := short variable declaration (combined with =), in case labels of switch statements, in map literal key-value pairs, and in type switches. The parser differentiates these uses.
Ellipsis
... is a three-dot token used for variadic parameters and argument expansion. In a function signature func f(args ...int), ... is a single token, not three separate dots. When passing a slice to a variadic function, slice... expands the slice.
func sum(nums ...int) int { /* ... */ }
total := sum(1, 2, 3) // works
numbers := []int{4, 5}
total = sum(numbers...) // token ... expands the slice
How the Lexer Reads Operator Tokens
The Go lexer applies the longest match rule: when multiple tokens could start at the same position, it picks the longest valid sequence. This ensures << becomes a single shift token, not two less-than tokens, and := is one token, not : followed by =.
Whitespace breaks multi-character tokens:
A space can prevent the longest match. <- is one token, but < - is two tokens: less-than and minus. The parser then interprets them accordingly, which may produce different program behavior or a syntax error.
Interaction with Automatic Semicolons
The semicolon insertion rules fire after certain tokens, including ), ], }, ++, and --. A newline after any of these inserts a semicolon, which can break code if you intended the next line to continue the current statement.
x := 1
++
This produces a compile error because the newline after 1 inserts a semicolon, making ++ a separate statement without an operand. Keep ++ and -- on the same line as their operand.
Common Mistakes and Misconceptions
- Using
=where==is needed.=is an assignment statement; it does not produce a boolean. The compiler rejectsif x = 5because the condition must be a boolean expression. - Treating
++and--as expressions. They are statements only—x = y++is a syntax error. - Confusing bitwise
&with logical&&.a & bworks only on integers; for booleans, use&&. - Forgetting that
:=is function-body only. At package level, usevar. - Misplacing a newline before
++or after&&. This can trigger unwanted semicolons. - Assuming
...is three separate dot tokens. It is one token;.. .is invalid syntax.
Semicolon insertion silently changes your code:
A line that ends with an identifier, literal, or one of the keywords break, continue, fallthrough, return, or any closing punctuation triggers a semicolon. When a compile error points to the next line for no obvious reason, check the end of the previous line.
Summary
Operators and punctuation tokens give Go its concise, readable syntax. The lexer reliably maps sequences like &^= and ... into single tokens, freeing the parser from character-level details. Understanding these tokens as lexical building blocks makes it easier to read code, debug syntax errors caused by semicolon insertion, and see why certain operator combinations compile while others do not.