Types and Structures

New Types

You can create your own types in Go using the type keyword. For example:

type Score int

func show(s Score) {
        fmt.Println("mark =", s)
}

func main() {
        s := Score(5)  // s is declared to be of type Score
        show(s)
}

In this example, int is said to be the underlying type of Score.

One reason to create types is to make your source code clearer. Another reason is that you can define methods on the type Score, but you cannot define methods on a basic type such as int.

You can call show like this:

show(5)

Strictly speaking, 5 is an int, and so it is not of type Score. However, because Score has an underlying type of int, this statement is not an error in Go.

Here is an example where you do get a type error. Suppose we add a new type called Mark:

type Mark int

And then call this code:

m := Mark(3)
show(m)  // compiler error: m is wrong type

This doesn’t compile because m is of type Mark, but show expects a parameter of type Score. Even though both Mark and Score have the same underlying type, one cannot be assigned to the other.

See the Go specification for the exact type equivalence rules.

Structures

Go structs are similar to the structures in C-like languages. For example, here is how you can represent a 2-dimensional point:

type Point struct {
        x, y int
}

One way to create a struct is to use the new function:

p := new(Point)  // new initializes p.x and p.y to their zero values
fmt.Println("p.x: ", p.x)
fmt.Println("p.y: ", p.y)

The new function always sets the variables in a struct to their zero values. If you want to initialize them to some other values, you can use a composite literal like this:

dest := Point{-8, 11}   // Point{-8, 11} is a composite literal
fmt.Println("dest.x: ", dest.x)
fmt.Println("dest.y: ", dest.y)

Note that new is not used here.

Here is an example of a struct built from two points:

type Segment struct {
        start, end Point
}

You can create a new Segment using a composite literal like this:

trip := Segment{Point{1, 2}, Point{3, 4}}
fmt.Printf("%v\n", trip)   // %v prints Go values in a nicely formatted way

This prints:

{{1 2} {3 4}}

Another way to write a composite literal is with explicit parameter names:

trip := Segment{start: Point{1, 2}, end: Point{3, 4}}

When explicit names are given like this, the order of the parameters doesn’t matter. The above declaration is the same as this:

trip := Segment{end: Point{3, 4}, start: Point{1, 2}}  // order doesn't matter
                                                       // when you provide
                                                       // names

Named composite literals are especially useful for longer, more complex structs where the order of parameters can be hard to remember.

Questions

  1. Explain the idea of an underlying type.
  2. Show how to define a new Go type called Name whose underlying type is string.
  3. When you create a struct value using new, what values are assigned to the variables in the struct?
  4. Show how to define a new Go struct called Person that contains the name of a person, and their age. Then write code that does the following:
    • Uses new to create a new Person whose name is Ray and whose age is 19.
    • Uses a composite literal to create a new Person whose name is Ann and whose age is 25.
    • Uses a composite literal with explicit parameter names to create a new person whose name is Pat and whose age is 30.