Contents

structs and named types

I recently blogged about patterns in go and in that we saw how methods on structs can be used to implement interfaces. Specifically the Stringer interface.

Structs, are not the only way to achieve this; all named types offer similar functionality. First - some definitions from the golang spec…

struct types

“A struct is a sequence of named elements, called fields, each of which has a name and a type. Field names may be specified explicitly (IdentifierList) or implicitly (AnonymousField). Within a struct, non-blank field names must be unique.” - golang spec

named types

“A type determines the set of values and operations specific to values of that type. Types may be named or unnamed. Named types are specified by a (possibly qualified) type name; unnamed types are specified using a type literal, which composes a new type from existing types.” - golang spec

Essentially this boils down to:

  • struct are a sequence of fields, each field has a type.
  • named types create a new type, using an existing underlying type.

code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// An empty struct.
struct {}

// A struct with 4 fields.
struct {
	x, y int
	u float32
	A *[]int
	F func()
}

Here the type is struct but we can create a new named type, with the underlying type of struct:

1
2
3
4
5
6
type NamedType struct {
	x, y int
	u float32
	A *[]int
	F func()
}

But named types don’t exclusively apply to the struct underlying type, any type can become a named type. If we take the example from patterns in go:

safe.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package safe

type mutator func(input string) string

type Str struct {
	val string
	mutate mutator
}

func (s Str) String() string {
	return s.mutate(s.val)
}

func Lower(input string) string {
    return strings.ToLower(input)
}

func Obfuscate(input string) string {
	return strings.Repeat("*", len(input))
}

func NewStr(v string, f mutator) Str {
    return Str{
        val:    v,
        mutate: f,
    }
}

This uses structs and a mutator function type to create a safe representation of a string, that won’t accidentally print a password to logs. We don’t have to use struct as the underlying type to hold the string, we can actually use a string to represent a string.

main.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
	"fmt"
	"strings"
)

type password string

func main() {
	var pass password = "super-secret-password"
	fmt.Println("password: ", pass)

}

func (p password) String() string {
	return strings.Repeat("*", len(p))
}

In this small go program the named type is password which has the underlying type string and implements the Stringer interface. Running it and the output is…

password: ********************* instead of password: super-secret-password

We can see that the String() method of the password type was called, and printed the safe version of the underlying string value.

There is a gotcha though, as named types are types if we have the type username with an underlying type of string, and introduce this to our program:

main.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
	"strings"
)

type username string
type password string

func main() {
	var user username = "[email protected]"
	var pass password = "super-secret-password"
	doWork(user, pass)

}

func (p password) String() string {
	return strings.Repeat("*", len(p))
}

func doWork(user, pass string) {
	...
}

The problem here is that if we try and compile this program we get the following error:

1
2
./main.go:13: cannot use user (type username) as type string in argument to doWork
./main.go:13: cannot use pass (type password) as type string in argument to doWork

Even though username and password types have the underlying type string, they are not of the same type, and more importantly they are not of the type string. We would have to type cast them to string to be able to use them here.

Or instead we can use their named types…

1
2
3
func doWork(user username, pass password) {
	...
}

This leads to incredible power, when using types idiomatically!