Basic composite types
In the :doc:`previous chapter<functions>`, we saw how to combine the traditional control flow idioms seen in :doc:`chapter 3<control>` to create blocks of reusable code, called functions. In this chapter, we'll see how to combine the basic data types seen in :ref:`chapter 2<primitive-types>`, in order to create complex data types.
Go comes with some primitive types: ints, floats, complex numbers, booleans, and strings. Fair enough, but you'll notice as you start to write complex programs that you'll need more advanced forms of data.
Let's begin with an example.
In the previous chapter, we wrote the
MAX(A, B int) function to find out
the bigger value between two integers A, and B. Now, suppose that A, and B are
actually the ages of persons whose names are respectively Tom and Bob. We need
our program to output the name of the older person, and by how many years he is
Sure, we still can use the
MAX(A, B int) function by giving it as input
parameters, the ages of Tom and Bob. But it'd be nicer to write a function
Older for people. And not for integers.
People! I said people! Thus the need to create a custom type to represent
people, and this type will be used for the input parameters of the
function we'd like to write.
In Go, as in C and other languages, we can declare new types that act as
containers for attributes or fields of some other types. For example, we can
create a custom type called
person to represent the attributes of a
person. In our example these attributes will be the person's
name and age.
A type like this is called a
struct, which is short for structure.
How to declare a struct in Go?
See? It's easy. A person
struct is a container of two fields:
- The person's name: a field of type
- The person's age: a field of type
To declare a
person variable we do exactly as we learned in
:doc:`chapter 2<basic>` for basic variable types.
Thus, writing things like:
We access and assign a struct's fields (attributes) with the dot nottation.
That is, if
P is a variable of type
person, then we access its fields like
There's even two short-form assignment statement syntaxes:
- By providing the values for each (and every) field in order:
- By using field:value initializers for any number of fields in any order:
Good. Let's write our
Older function that takes two input parameters of type
person, and returns the older one and the difference in their ages.
And that's about it!
struct types are handy, easy to declare and to use!
Ready for another problem?
Now, suppose that we'd like to retrieve the older of, not 2, but 10 persons!
Would you write a
Older10 function that would take 10 input
Sure you can! But, seriously, that would be one ugly, very ugly function! Don't
write it, just picture it in your brain. Still not convinced? Ok, imagine a
Older100 function with 100 input parameters! Convinced, now?
Ok, let's see how to solve these kinds of problems using arrays.
An array of size n is a block of n consecutive objects of the same type. That is a finite set of indexed objects, where you can enumerate them: object number 1, object number 2, etc...
In fact, in Go as in C and other languages, the indexing starts from 0, so you actually say: object number 0, object number 1... object number n-1
Here's how to declare an array of 10
ints, for example:
And here's how to declare an array of 10
We can access the x th element of an array
A, with the syntax:
Specifically we say:
For example, the name of the 3 rd person is:
The idea now is to write a function
Older10 that takes an array of 10
persons as its input, and returns the older
person in this array.
We could have declared and initialized the variable
array of the
function above in a single shot like this:
This means that to initialize an array, you put its elements between two braces, and you separate them with commas.
We can even omit the size of the array, and Go will count the number of elements given in the initialization for us. So we could have written the above code as:
Specifically note the ellipsis
However, the size of an array is an important part of its definition. They don't grow and they don't shrink. You can't, for example, have an array of 9 elements, and use it with a function that expects an array of 10 elements as an input. Simply because they are of different types. Remember this.
Some things to keep in mind:
- Arrays are values. Assigning one array to another, copies all of the elements
of the right hand array to the left hand array.
That is: if
Bare arrays of the same type, and we write:
B = A, then
B == A,
B == A...
B[n-1] == A[n-1].
- When passing an array to a function, it is copied. i.e. The function recieves a copy of that array, and not a reference (pointer) to it.
- Go comes with a built-in function
lenthat returns variables sizes. In the previous example:
len(array) == 10.
You can think to yourself: "Hey! I want an array of arrays!". Yes, that's possible, and often used in practice.
We can declare a 2-dimensional array like this:
This is an array of (2 arrays (of 4
int)). We can think of it as a matrix,
or a table of two lines each made of 4 columns.
The previous declaration may be simplified, using the fact that the compiler can count arrays' elements for us, like this:
Guess what? Since Go is about cleaner code, we can simplify this code even further:
Cool, eh? Now, we can combine multiple fields of different types to create a
struct and we can have
arrays of, as many as we want, of objects of the
same type, and pass it as a single block to our functions. But this is just
the begining! In the next chapter, we will see how to create and use some
advanced composite data types with pointers.