Go is a systems programming language intended to be a general-purpose systems language, like C++. These are some notes on Go for experienced C++ programmers. This document discusses the differences between Go and C++, and says little to nothing about the similarities.
For a more general introduction to Go, see the Go tutorial and Effective Go.
For a detailed description of the Go language, see the Go spec.
const
or volatile
qualifiers.
nil
for invalid pointers, where C++ uses
NULL
or simply 0
.
The declaration syntax is reversed compared to C++. You write the name followed by the type. Unlike in C++, the syntax for a type does not match the way in which the variable is used. Type declarations may be read easily from left to right.
Go C++ var v1 int // int v1; var v2 string // const std::string v2; (approximately) var v3 [10]int // int v3[10]; var v4 []int // int* v4; (approximately) var v5 struct { f int } // struct { int f; } v5; var v6 *int // int* v6; (but no pointer arithmetic) var v7 map[string]int // unordered_map<string, int>* v7; (approximately) var v8 func(a int) int // int (*v8)(int a);
Declarations generally take the form of a keyword followed by the name
of the object being declared. The keyword is one of var
,
func
,
const
, or type
. Method declarations are a minor
exception in that
the receiver appears before the name of the object being declared; see
the discussion of interfaces.
You can also use a keyword followed by a series of declarations in parentheses.
var ( i int m float )
When declaring a function, you must either provide a name for each parameter or not provide a name for any parameter; you can't omit some names and provide others. You may group several names with the same type:
func f(i, j, k int, s, t string)
A variable may be initialized when it is declared. When this is done, specifying the type is permitted but not required. When the type is not specified, the type of the variable is the type of the initialization expression.
var v = *p
See also the discussion of constants, below. If a variable is not initialized explicitly, the type must be specified. In that case it will be implicitly initialized to the type's zero value (0, nil, etc.). There are no uninitialized variables in Go.
Within a function, a short declaration syntax is available with
:=
.
v1 := v2
This is equivalent to
var v1 = v2
Go permits multiple assignments, which are done in parallel.
i, j = j, i // Swap i and j.
Functions may have multiple return values, indicated by a list in parentheses. The returned values can be stored by assignment to a list of variables.
func f() (i int, j int) { ... } v1, v2 = f()
Go code uses very few semicolons in practice. Technically, all Go statements are terminated by a semicolon. However, Go treats the end of a non-blank line as a semicolon unless the line is clearly incomplete (the exact rules are in the language specification). A consequence of this is that in some cases Go does not permit you to use a line break. For example, you may not write
func g() { // INVALID }A semicolon will be inserted after
g()
, causing it to be
a function declaration rather than a function definition. Similarly,
you may not write
if x { } else { // INVALID }A semicolon will be inserted after the
}
preceding
the else
, causing a syntax error.
Since semicolons do end statements, you may continue using them as in
C++. However, that is not the recommended style. Idiomatic Go code
omits unnecessary semicolons, which in practice is all of them other
than the initial
While we're on the topic, we recommend that rather than worry about
semicolons and brace placement, you format your code with
the
When using a pointer to a struct, you use
Go does not require parentheses around the condition of a
Go does not have a
Go permits
In a
But a
The values in a
The
The
In Go constants may be untyped. This applies even to constants
named with a
The language does not impose any limits on the size of an untyped
numeric constant or constant expression. A limit is only applied when
a constant is used where a type is required.
Go does not support enums. Instead, you can use the special name
A slice is conceptually a struct with three fields: a
pointer to an array, a length, and a capacity.
Slices support
the
Given an array, or another slice, a new slice is created via
What this means is that Go uses slices for some cases where C++ uses pointers.
If you create a value of type
The slice syntax may also be used with a string. It returns a new string,
whose value is a substring of the original string.
Because strings are immutable, string slices can be implemented
without allocating new storage for the slices's contents.
Go has a builtin function
Map and channel values must be allocated using the builtin function
The
Where C++ provides classes, subclasses and templates,
Go provides interfaces. A
Go interface is similar to a C++ pure abstract class: a class with no
data members, with methods which are all pure virtual. However, in
Go, any type which provides the methods named in the interface may be
treated as an implementation of the interface. No explicitly declared
inheritance is required. The implementation of the interface is
entirely separate from the interface itself.
A method looks like an ordinary function definition, except that it
has a receiver. The receiver is similar to
the
This declares a method
Methods are defined on named types. If you convert the value
to a different type, the new value will have the methods of the new type,
not the old type.
You may define methods on a builtin type by declaring a new named type
derived from it. The new type is distinct from the builtin type.
Given this interface:
we can make
Now any function which takes
In other words, if we view
An anonymous field may be used to implement something much like a C++ child
class.
This effectively implements
The
This is not precisely the same as a child class in C++.
When a method of an anonymous field is called,
its receiver is the field, not the surrounding struct.
In other words, methods on anonymous fields are not virtual functions.
When you want the equivalent of a virtual function, use an interface.
A variable which has an interface type may be converted to have a
different interface type using a special construct called a type assertion.
This is implemented dynamically
at runtime, like C++
The conversion to
Because the conversion is dynamic, it may be used to implement generic
programming similar to templates in C++. This is done by
manipulating values of the minimal interface.
Containers may be written in terms of
Go permits starting a new thread of execution (a goroutine)
using the
Internally, goroutines act like coroutines that are multiplexed among
multiple operating system threads. You do not have to worry
about these details.
(Note that the
Goroutines are (intended to be) cheap.
Function literals (which Go implements as closures)
can be useful with the
Channels are used to communicate between goroutines. Any value may be
sent over a channel. Channels are (intended to be) efficient and
cheap. To send a value on a channel, use
The Go library provides mutexes, but you can also use
a single goroutine with a shared channel.
Here is an example of using a manager function to control access to a
single value.
In that example the same channel is used for input and output.
This is incorrect if there are multiple goroutines communicating
with the manager at once: a goroutine waiting for a response
from the manager might receive a request from another goroutine
instead.
A solution is to pass in a channel.
To use gofmt
program. That will produce a single standard
Go style, and let you worry about your code rather than your
formatting. While the style may initially seem odd, it is as good as
any other style, and familiarity will lead to comfort.
.
instead
of ->
.
Thus syntactically speaking a structure and a pointer to a structure
are used in the same way.
type myStruct struct { i int }
var v9 myStruct // v9 has structure type
var p9 *myStruct // p9 is a pointer to a structure
f(v9.i, p9.i)
if
statement, or the expressions of a for
statement, or the value of a
switch
statement. On the other hand, it does require curly braces
around the body of an if
or for
statement.
if a < b { f() } // Valid
if (a < b) { f() } // Valid (condition is a parenthesized expression)
if (a < b) f() // INVALID
for i = 0; i < 10; i++ {} // Valid
for (i = 0; i < 10; i++) {} // INVALID
while
statement nor does it have a
do/while
statement. The for
statement may be used with a single condition,
which makes it equivalent to a while
statement. Omitting the
condition entirely is an endless loop.
break
and continue
to specify a label.
The label must
refer to a for
, switch
, or select
statement.
switch
statement, case
labels do not fall
through. You can
make them fall through using the fallthrough
keyword. This applies
even to adjacent cases.
switch i {
case 0: // empty case body
case 1:
f() // f is not called when i == 0!
}
case
can have multiple values.
switch i {
case 0, 1:
f() // f is called if i == 0 || i == 1.
}
case
need not be constants—or even integers;
any type
that supports the equality comparison operator, such as strings or
pointers, can be used—and if the switch
value is omitted it defaults to true
.
switch {
case i < 0:
f1()
case i == 0:
f2()
case i > 0:
f3()
}
++
and --
operators may only be used in
statements, not in expressions.
You cannot write c = *p++
. *p++
is parsed as
(*p)++
.
defer
statement may be used to call a function after
the function containing the defer
statement returns.
fd := open("filename")
defer close(fd) // fd will be closed when this function returns.
Constants
const
declaration, if no
type is given in the declaration and the initializer expression uses only
untyped constants.
A value derived from an untyped constant becomes typed when it
is used within a context that
requires a typed value. This permits constants to be used relatively
freely without requiring general implicit type conversion.
var a uint
f(a + 1) // untyped numeric constant "1" becomes typed as uint
const huge = 1 << 100
f(huge >> 98)
iota
in a single const
declaration to get a
series of increasing
value. When an initialization expression is omitted for a const
,
it reuses the preceding expression.
const (
red = iota // red == 0
blue // blue == 1
green // green == 2
)
Slices
[]
operator to access elements of the underlying array.
The builtin
len
function returns the
length of the slice. The builtin cap
function returns the
capacity.
a[I:J]
. This
creates a new slice which refers to a
, starts at
index I
, and ends before index
J
. It has length J - I
.
The new slice refers to the same array
to which a
refers. That is, changes made using the new slice may be seen using
a
. The
capacity of the new slice is simply the capacity of a
minus
I
. The capacity
of an array is the length of the array. You may also assign an array pointer
to a variable of slice type; given var s []int; var a[10] int
,
the assignment s = &a
is equivalent to
s = a[0:len(a)]
.
[100]byte
(an array of 100 bytes,
perhaps a
buffer) and you want to pass it to a function without copying it, you should
declare the function parameter to have type []byte
, and pass the
address
of the array. Unlike in C++, it is not
necessary to pass the length of the buffer; it is efficiently accessible via
len
.
Making values
new
which takes a type and
allocates space
on the heap. The allocated space will be zero-initialized for the type.
For example, new(int)
allocates a new int on the heap,
initializes it with the value 0
,
and returns its address, which has type *int
.
Unlike in C++, new
is a function, not an operator;
new int
is a syntax error.
make
.
A variable declared with map or channel type without an initializer will be
automatically initialized to nil
.
Calling make(map[int]int)
returns a newly allocated value of
type map[int]int
.
Note that make
returns a value, not a pointer. This is
consistent with
the fact that map and channel values are passed by reference. Calling
make
with
a map type takes an optional argument which is the expected capacity of the
map. Calling make
with a channel type takes an optional
argument which sets the
buffering capacity of the channel; the default is 0 (unbuffered).
make
function may also be used to allocate a slice.
In this case it
allocates memory for the underlying array and returns a slice referring to it.
There is one required argument, which is the number of elements in the slice.
A second, optional, argument is the capacity of the slice. For example,
make([]int, 10, 20)
. This is identical to
new([20]int)[0:10]
. Since
Go uses garbage collection, the newly allocated array will be discarded
sometime after there are no references to the returned slice.
Interfaces
this
pointer in a C++ class method.
type myType struct { i int }
func (p *myType) get() int { return p.i }
get
associated with myType
.
The receiver is named p
in the body of the function.
type myInteger int
func (p myInteger) get() int { return int(p) } // Conversion required.
func f(i int) { }
var v myInteger
// f(v) is invalid.
// f(int(v)) is valid; int(v) has no defined methods.
type myInterface interface {
get() int
set(i int)
}
myType
satisfy the interface by adding
func (p *myType) set(i int) { p.i = i }
myInterface
as a parameter
will accept a
variable of type *myType
.
func getAndSet(x myInterface) {}
func f1() {
var p myType
getAndSet(&p)
}
myInterface
as a C++ pure abstract
base
class, defining set
and get
for
*myType
made *myType
automatically
inherit from myInterface
. A type may satisfy multiple interfaces.
type myChildType struct { myType; j int }
func (p *myChildType) get() int { p.j++; return p.myType.get() }
myChildType
as a child of
myType
.
func f2() {
var p myChildType
getAndSet(&p)
}
set
method is effectively inherited from
myChildType
, because
methods associated with the anonymous field are promoted to become methods
of the enclosing type. In this case, because myChildType
has an
anonymous field of type myType
, the methods of
myType
also become methods of myChildType
.
In this example, the get
method was
overridden, and the set
method was inherited.
dynamic_cast
. Unlike
dynamic_cast
, there does
not need to be any declared relationship between the two interfaces.
type myPrintInterface interface {
print()
}
func f3(x myInterface) {
x.(myPrintInterface).print() // type assertion to myPrintInterface
}
myPrintInterface
is entirely dynamic.
It will
work as long as the underlying type of x (the dynamic type) defines
a print
method.
type Any interface { }
Any
, but the caller
must unbox using a type assertion to recover
values of the contained type. As the typing is dynamic rather
than static, there is no equivalent of the way that a C++ template may
inline the relevant operations. The operations are fully type-checked
at runtime, but all operations will involve a function call.
type iterator interface {
get() Any
set(v Any)
increment()
equal(arg *iterator) bool
}
Goroutines
go
statement. The go
statement runs a function in a
different, newly created, goroutine.
All goroutines in a single program share the same address space.
func server(i int) {
for {
print(i)
sys.sleep(10)
}
}
go server(1)
go server(2)
for
statement in the server
function is equivalent to a C++ while (true)
loop.)
go
statement.
var g int
go func(i int) {
s := 0
for j := 0; j < i; j++ { s += j }
g = s
}(1000) // Passes argument 1000 to the function literal.
Channels
<-
as a binary
operator. To
receive a value on a channel, use <-
as a unary operator.
When calling
functions, channels are passed by reference.
type cmd struct { get bool; val int }
func manager(ch chan cmd) {
var val int = 0
for {
c := <- ch
if c.get { c.val = val ch <- c }
else { val = c.val }
}
}
type cmd2 struct { get bool; val int; ch <- chan int }
func manager2(ch chan cmd2) {
var val int = 0
for {
c := <- ch
if c.get { c.ch <- val }
else { val = c.val }
}
}
manager2
, given a channel to it:
func f4(ch <- chan cmd2) int {
myCh := make(chan int)
c := cmd2{ true, 0, myCh } // Composite literal syntax.
ch <- c
return <-myCh
}