[Book] Learning Go

First Post:

Last Update:

Word Count:
4.4k

Read Time:
27 min

El libro

Introduction

This article is used to keep notes and summaries of the book “Learning Go”.
The content will be continuously updated as I read through the book.

Chapter.1 - Setting Up Your Go Environment

The go command

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("Hello, world!")
}
1
> go run hello.go
1
> go build hello.go

This creates an executable called hello (or hello.exe on Windows) in the current directory.

Makefiles

Here’s a sample Makefile to add to our very simple project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.DEFAULT_GOAL := build

fmt:
go fmt ./...
.PHONY:fmt

lint: fmt
golint ./...
.PHONY: lint

vet: fmt
go vet ./...
.PHONY:vet

build: vet
go build hello.go
.PHONY:build

Each possible operation is called a target. The .DEFAULT_GOAL defines which target is run when no target is specified. In our case, we are going to run the build target.

The world before the colon (:) is the name of the target. Any words after the target (like vet in the line build: vet) are the other targets that must be run before the specified target runs. The taks that are performed by the target are on the indented lines after the target (The)

Once theMakefile is in your directory, type:

1
> make

Chapter.2 - Primative Types and Declarations

Complex Number

1
2
3
4
5
func main() {
x := complex(2.5, 3.1)
y := complex(10.2, 2)
fmt.Println(x + y)
}

var vs. :=

1
var x int = 10
1
var x = 10
1
var x int
1
var x, y int = 10, 20
1
var x, y int
1
var x, y = 10, "hello"
1
2
3
4
5
6
7
var (
x int
y = 20
z int = 30
d, e, = 40, "hello"
f, g string
)
1
2
var x = 10
x := 10
1
2
var x, y = 10, "hello"
x, y := 10, "hello"
1
2
x := 10
x, y := 30, "hello"

const

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const x int64 = 10
const (
idKey = "id"
nameKey = "name"
)

const z = 20 * 10

func main() {
const y = "hello"

fmt.Println(x)
fmt.Println(y)

x = x + 1
y = "bye"

fmt.Println(x)
fmt.Println(y)
}

Output:
1
2
3
4
./prog.go:21:2: cannot assign to x (neither addressable nor a map index expression)
./prog.go:22:2: cannot assign to y (neither addressable nor a map index expression)

Go build failed.


Typed and Untyped Constant

Untyped constant:

1
const x = 10

All of the following assignments are legal:

1
2
3
var y int = x
var x float64 = x
var d byte = x

Here’s what a typed constant declaration looks like:

1
const typedX int = 10

This constant can only be assigned directly to an int. Assigning it to any other type produces a compile-time error.

Unused Variables

1
2
3
4
5
6
func main() {
x := 10
x = 20
fmt.Println(x)
x = 30
}

The Go compiler allows you to create unread constants with const. This is because constants in Go are calculated at compile time and cannot have any side effects. This makes them easy to eliminate: if a constant isn’t used, it is simply not included in the compiled binary.

Naming Variables and Constants

1
2
3
4
5
6
7
8
9
10
//Example 2-4. Variable names you should never use
_0 := 0_0
_𝟙 := 20
π := 3 //pi
a := "hello" //Full-width (Shift + Space)

fmt.Println(_0)
fmt.Println(_𝟙)
fmt.Println(π)
fmt.Println(a)

asd
While this code works DO NOT name your variable like this.

Chapter.3 - Composite Types

Arrays——Too Rigid to Use Directly

Arrays are rarely used directly in Go.

All of the elements in the array must be of the type that’s specified (this doesn’t mean they are always of the same type). There are a few different declaration styles.

1
var x [3]int

1
var x = [3]int{10, 20, 30}
1
2
3
var x = [12]int{1, 5: 4, 6, 10: 100, 15}
//This creates an array of 12 ints with the following values: [1, 0, 0, 0, 0, 4, 6, 0, 0, 0,
100, 15].

When using an array literal to initialize an array, you can leave off the number and use ... instead:

1
var x = [...]int{10, 20, 30}

You can use == and != to compare arrays:

1
2
3
var x = [...]int{1, 2, 3}
var y = [3]int{1, 2, 3}
fmt.Println(x == y) // prints true

Go only has one-dimensional arrays, but you can simulate multidimensional arrays:

1
var x [2][3]int

This declares x to be an array of length 2 whose type is an array of ints of length 3.

Note that you cannot read or write past the end of an array or use negative index. If you do this with a constant or literal index, it is a compile-time error.

Finally, the built-in funciton len takes in an array and returns its length:

1
fmt.Println(len(x))

Go considers the size of the array to be part of the type of the array. This makes an array that’s declared to be [3]int a different type from an array that’s declared to be [4]int. This also means that you cannot use a variable to specify the size of an array, because types must be resolved at compile time, not at runtime.

You cannot use a type conversion to convert arrays of different sizes to identical types.

Slices

Most of the time, when you want a data structure that holds a sequence of values, a slice is what you should use. WHat makes slices so useful is that the length is not part of the type for a slice.

1
var x = []int{10, 20, 30}

Using [...] makes an array. Using [] makes a slice.

1
var x = []int{1, 5: 4, 6, 10: 100, 15}

You can simulate multidimensional slices and make a slice of slices:

1
var x [][]int


1
2
x[0] = 10
fmt.Println(x[2])

In Go, nil is an identifier that represents the lack of a value for some types. Like the untyped numeric constants we saw in the previous chapter,nil has no type, so it can be assigned or compared against values of different types. A nil slice contains nothing.

A slice is the type that isn’t comparable. It is a compile-time error to use == to see if two slices are identical or != to see if they are different. The only thing you can compare a slice with is nil:

1
fmt.Println(x == nil)

append

The built-in append function is used to grow slices

1
2
var x []int
x = append(x, 10)


The append function takes at least two parameters, a slice of any type and a value of that type. It returns a slice of the same type.

1
2
var x = []int{1, 2, 3}
x = append(x, 4)


You can append more than one value at a time:

1
x = append(x, 5, 6, 7)

Capacity

1
2
3
4
5
6
7
8
9
10
11
12
var x[]int
fmt.Println(x, len(x), cap(x))
x = append(x, 10)
fmt.Println(x, len(x), cap(x))
x = append(x, 20)
fmt.Println(x, len(x), cap(x))
x = append(x, 30)
fmt.Println(x, len(x), cap(x))
x = append(x, 40)
fmt.Println(x, len(x), cap(x))
x = append(x, 50)
fmt.Println(x, len(x), cap(x))

make

This built-in make function allows us to create an empty slice that already has a length or capacity specified. It allows us to specify the type, length, and, optionally, he capacity.

1
x := make([]int, 5)

1
2
x := make([]int, 5)
x = append(x, 10)
1
x := make([]int, 5, 10)

You can also create a slice with zero length, but a capacity that’s greater than zero:

1
x := make([]int, 0, 10)

In this case, we have a non-nil slice with a length of 0, but a capacity of 10. Since the length is 0, we can’t directly index into it, but we can append values to it:
1
2
x := make([]int, 0, 10)
x = append(x, 5, 6, 7, 8)

The value of x is now [5 6 7 8], with a length of 4 and a capacity of 10.

Never specify a capacity that’s less than the length! It is a compil-time error to do so with a constant or numeric literal. If you use a variable to specify a capacity that’s smaller than the length, your program will panic at runtime.

Declaring Yoyr Slice

1
2
//Example 3-2: Declaring a slice that might stay nil
var data []int

You can create a slice using an empty slice literal:

1
var x = []int{}

Declaring a slice with default values

1
data := []int{2, 4, 6, 8} //numbers we appreciate

Slicing Slices

A slice expression creates a slice from a slice. It’s written inside brackets and consists of a starting offset and an ending offset, separated by a colon (:).

1
2
3
4
5
6
7
8
9
10
x := []int{1, 2, 3, 4}
y := x[:2]
z := x[1:]
d := x[1:3]
e := x[:]
fmt.Println("x:", x)
fmt.Println("y:", y)
fmt.Println("z:", z)
fmt.Println("d:", d)
fmt.Println("e:", e)

Output:
1
2
3
4
5
x: [1 2 3 4]
y: [1 2]
z: [2 3 4]
d: [2 3]
e: [1 2 3 4]


When you take a slice from a slice, you are NOT making a copy of the data. Instead, you now have two variables that are sharing memory. This means that changes to an element in a slice affect al slices that share that element.

1
2
3
4
5
6
7
8
9
x := []int{1, 2, 3, 4}
y := x[:2]
z := x[1:]
x[1] = 20
y[0] = 10
z[1] = 30
fmt.Println("x:", x)
fmt.Println("y:", y)
fmt.Println("z:", z)

Output:

1
2
3
x: [10 20 30 4]
y: [10 20]
z: [20 30 4]


Slicing slices gets extra confusing when combined with append:

1
2
3
4
5
6
7
x := []int{1, 2, 3, 4}
y := x[:2]

fmt.Println(cap(x), cap(y))
y = append(y, 30)
fmt.Println("x: ", x)
fmt.Println("y: ", y)

Output:
1
2
3
4 4
x: [1 2 30 4]
y: [1 2 30]

Whenever you take a slice from another slice, the subslice’s capacity is set to the capacity of the original slice, minus the offset of the subslice within the original slice. This means that any unused capacity in the original slice is also shared with any subslices.

When we make the y slice from x, the length is set to 2, but the capacity is set to 4, the same as x. Since the capacity is 4, appending onto the end of y puts the value in the third position of x.

This behavior creates some very odd scenarios, with multiple slices appending and overwriting each other’s data:

1
2
3
4
5
6
7
8
9
10
11
12
//A more confusing example
x := make([]int, 0, 5)
x = append(x, 1, 2, 3, 4)
y := x[:2]
z := x[2:]
fmt.Println(cap(x), cap(y), cap(z))
y = append(y, 30, 40, 50)
x = append(x, 60)
z = append(z, 70)
fmt.Println("x:", x)
fmt.Println("y:", y)
fmt.Println("z:", z)

Output:

1
2
3
4
5 5 3
x: [1 2 30 40 70]
y: [1 2 30 40 70]
z: [30 40 70]

To avoid complicated slice situations, you should either never use append with a subslice or make sure that append doesn’t cause an overwrite by using a full slice expression:

1
2
y := x[:2:2]
z := x[2:4:4]

Converting Arrays to Slices

1
2
3
4
5
6
x := [4]int{5, 6, 7, 8}
y := x[:2]
z := x[2:]
x[0] = 10
fmt.Println("x:", x)
fmt.Println("y:", y)

Output:

1
2
3
x: [10 6 7 8]
y: [10 6]
z: [7 8]

copy

1
2
3
4
x := []int{1, 2, 3, 4}
y := make([]int, 4)
num := copy(y, x)
fmt.Println(y, num)

Output:

1
[1 2 3 4] 4


1
2
3
x := []int{1, 2, 3, 4}
y := make([]int, 2)
copy(y, x[2:])

1
2
3
x := []int{1, 2, 3, 4}
num = copy(x[:3], x[1:])
fmt.Println(x, num)

1
2
3
4
5
6
x := []int{1, 2, 3, 4}
d := [4]int{5, 6, 7, 8}
y := make([]int, 2)
copy(y, d[:])
fmt.Println(y)
copy(d[:], x)

Output:

1
2
[5 6]
[1 2 3 4]

Strings and Runes and Bytes

1
2
var s string = "Hello there"
var b byte = s[6]
1
2
3
4
var s string = "Hello there"
var s2 string = s[4:7]
var s3 string = s[:5]
var s4 string = s[6:]

Maps

map[keyType]valueType

1
var nilMap map[string]int

1
totalWins := map[string]int{}

1
2
3
4
5
teams := map[string][]string {
"Orcas": []string{"Fred", "Ralph", "Bijou"},
"Lions": []string{"Sarah", "Peter", "Billie"},
"Kittens": []string{"Waldo", "Raul", "Ze"},
}

1
ages := make(map[int][]string, 10)

  • Maps automatically grow as you add key-value pairs to them.
  • If you know how many key-value pairs you plan to insert into a map, you can use make to create a map with a specific initial size.
  • Passing a map to the len function tells you the number of key-value pairs in a map.
  • The zero value for a map is nil
  • Maps are not comparable. You can check if they are equal to nil, but you cannot check if two maps have identical keys and values using == or differ using !=

The key for a map can be any comparable type. This means you cannot use a slice or a map as the key for a map.

Learn more about hash map: GopherCon 2016, Inside the
Map Implementation.

Reading and Writing a Map

1
2
3
4
5
6
7
8
9
totalWins := map[string]int{}
totalWins["Orcas"] = 1
totalWins["Lions"] = 2
fmt.Println(totalWins["Orcas"])
fmt.Println(totalWins["Kittens"]) //Output: 0
totalWins["Kittens"]++
fmt.Println(totalWins["Kittens"]) //Output: 1
totalWins["Lions"] = 3
fmt.Println(totalWins["Lions"])

The comma ok Idiom

1
2
3
4
5
6
7
8
9
10
11
12
m := map[string]int{
"hello": 5,
"world": 0,
}
v, ok := m["hello"]
fmt.Println(v, ok)

v, ok := m["world"]
fmt.Println(v, ok)

v, ok := m["goodbye"]
fmt.Println(v, ok)

Rather than assign the result of a map read to a single variable, with the comma ok idiom you assign the results of a map read to two variables. The first gets the value associated with the key. The second value returned is a bool. It is usually named ok. If ok is true, the key is present in the map. If ok is false, the key is not present.

Deleting from Maps

1
2
3
4
5
m := map[string]int{
"hello": 5,
"world": 10,
}
delete(m, "hello")

Using Maps as Sets

1
2
3
4
5
6
7
8
9
10
11
intSet := map[int]bool{}
vals := []int{5, 10, 2, 5, 8, 7, 3, 9, 1, 2, 10}
for _, v := range vals {
intSet[v] = true
}
fmt.Println(len(vals), len(intSet))
fmt.Println(intSet[5])
fmt.Println(intSet(500))
if intSet[100] {
fmt.Println("100 is in the set")
}

Structs

Go doesn’t have classes, because it doesn’t have inheritance. This doesn’t mean Go doesn’t have some of the features of object-oriented languages, it just does things a little differently.

1
2
3
4
5
type person struct {
name string
age int
pet string
}
1
var fred person
1
bob := personP{}
1
2
3
4
5
julia := person{
"Julia",
40,
"cat",
}
1
2
3
4
beth := person{
age: 30,
name; "Beth",
}
1
2
beth.name = "Bob"
fmt.Println(beth.name)

Anonymous Structs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var person struct {
name string
age int
pet string
}

person.name = "bob"
person.age = 50
person.pet = "dog"

pet := struct {
name string
kind string
}{
name: "Fido",
kind: "dog",
}

Comparing and Converting Structs

Whether or not a struct is comparable depends on the struct’s field. Structs that are entirely composed of comparable types are comparable; those with slice or mmap fields are not.

1
2
3
4
type firstPerson struct {
name string
age int
}

We can use a type conversion to convert an instance of firstPerson to secondPerson, but we cannot use == to compare an instance of firstPerson and an instance of secondPerson, because they are different types:

1
2
3
4
type secondPerson struct {
name string
age int
}

We can’t convert an instance of firstPerson to thirdPerson, because the fields are in different order:
1
2
3
4
type thirdPerson struct {
age int
name string
}

We can’t convert an instance of firstPerson to fourthPerson, because the fields names don’t match:
1
2
3
4
type fourthPerson struct {
firstName string
age int
}

we can’t convert an instance of firstPerson to fifthPerson because there’s
an additional field:
1
2
3
4
5
type fifthPerson struct {
name string
age int
favoriteColor string
}

Anonymous structs add a small twist to this: if two struct variables are being compared
and at least one of them has a type that’s an anonymous struct, you can compare
them without a type conversion if the fields of both structs have the same names,
order, and types
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type firstPerson struct {
name string
age int
}
f := firstPerson{
name: "Bob",
age: 50,
}
var g struct {
name string
age int
}
// compiles -- can use = and == between identical named and anonymous structs
g = f
fmt.Println(f == g)

Chapter.4 - Blocks, Shadows, and Control Structures

Blocks

Each place where a declaration occurs is called a block. Variables, constants, types, and functions declared outside of any functions are placed in the package block.

Shadowing Variables

1
2
3
4
5
6
7
8
9
10
func main() {
x := 10
if x > 5 {
fmt.Println(x)
x := 5
fmt.Println(x)
}

fmt.Println(x)
}

Output:

1
2
3
10
5
10

A shadowing variable is a variable that has the same name as a variable in a containing block. For as long as the shadowing variable exists, you cannot access a shadowed variable.


1
2
3
4
5
6
7
8
9
func main() {
x := 10
if x > 5 {
x, y := 5, 20
fmt.Println(x, y)
}

fmt.Println(x)
}

Output:

1
2
5 20
10


if

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

import "fmt"
import "math/rand"

func main() {
n := rand.Intn(10)
if n == 0 {
fmt.Println("That's too low")
} else if n > 5 {
fmt.Println("That's too big:", n)
} else {
fmt.Println("That's a good number:", n)
}
}

If you run this code, you’wll find that it always assigns 1 to n. This happends because the default random number seed in math/rand is hard-coded.

Go doesn’t need parenthesis around the condition. But there’s another feature that Go adds to if statements that helps you better manager your variables.


1
2
3
4
5
6
7
8
//Example 4.6: Scoping a variable to an if statement
if n := rand.Intn(10); n == 0 {
fmt.Println("That's too low")
} else if n > 5 {
fmt.Println("That's too big:", n)
} else {
fmt.Println("That's a good number:", n)
}

It lets you create variables that are available only where they are needed. Once the series of if/else statements ends, n is undefined.

for, Four Ways

1
2
3
4
//Example 4-8. A complete for statement
for i := 0; i < 10; i++ {
fmt.Println(i)
}
1
2
3
4
5
6
//Example 4-9. A condition-only for statement
i := 1
for i < 100 {
fmt.Println(i)
i = i * 2
}
1
2
3
4
5
6
//Example 4-10. Infinite loop
func main() {
for {
fmt.Println("Hello")
}
}

break and continue

There is no Go equivalent of the do keyword in Java, C and JavaScript. If you want to iterate at least once, the cleanest way is to use an infinite for loop that ends with an if statement:

1
2
3
4
5
6
for {
//do something.
if !CONDITION {
break
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Example 4-11. Confusing code
for i := 1; i <= 100; i++ {
if i%3 == 0 {
if i%5 == 0 {
fmt.Println("FizzBuzz")
} else {
fmt.Println("Fizz")
}
} else if i%5 == 0 {
fmt.Println("Buzz")
} else {
fmt.Println(i)
}
}

Go encourages short if statement bodies, as left-align as possible. Nested code is difficult to follow. Using a continue statement makes it easier to understand what’s going on.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Example 4-12. Using continue to make code clearer
for i := 1; i <= 100; i++ {
if i%3 == 0 && i%5 == 0 {
fmt.Println("FizzBuzz")
continue
}
if i%3 == 0 {
fmt.Println("Fizz")
continue
}
if i%5 == 0 {
fmt.Println("Buzz")
continue
}
fmt.Println(i)
}

The for-range Statement

1
2
3
4
5
//Example 4-13. The for-range loop
evenVals := []int{2, 4, 6, 8, 10, 12}
for i, v := range evenVals {
fmt.Println(i, v)
}

Output:

1
2
3
4
5
6
0 2
1 4
2 6
3 8
4 10
5 12

The first variable is the position in the data structure being iterated, while the second is the value at that position.
1
2
3
4
5
//Example 4-14. Ignoring the key in a for-range loop
evenVals := []int{2, 4, 6, 8, 10, 12}
for _, v := range evenVals {
fmt.Println(v)
}

Output:
1
2
3
4
5
6
2
4
6
8
10
12

Iterating over maps

1
2
3
4
5
6
7
8
9
10
11
12
m := map[string]int{
"a": 1,
"c": 3,
"b": 2,
}

for i := 0; i < 3; i++ {
fmt.Println("Loop", i)
for k, v := range m {
fmt.Println(k, v)
}
}

Output:

1
2
3
4
5
6
7
8
9
10
11
12
Loop 0
c 3
b 2
a 1
Loop 1
a 1
c 3
b 2
Loop 2
a 1
c 3
b 2

The order of the keys and values varies; some runs may be identical. This is actually a security feature. In earlier Go versions, the iteration order for keys in a map was usually (but not always) the same if you inserted the same items into a map. This caused two problems:

  • People would write code that assumed that the order was fixed, and this would break at weired times.
  • If maps always hash items to the exact same values, and you know that a server is storing some user data in a map, you can actually slow down a server with an attack called Hash DoS by sending it specially crafted data where all of the keys hash to the same bucket.

Iterating over strings

1
2
3
4
5
6
7
8
samples := []string{"hello", "apple_π!"}
for _, sample := range samples {
for i, r := range sample {
fmt.Println(i, r, string(r))
}

fmt.Println()
}

Output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0 104 h
1 101 e
2 108 l
3 108 l
4 111 o

0 97 a
1 112 p
2 112 p
3 108 l
4 101 e
5 95 _
6 960 π
8 33 !

In the first column, we have the index; in the second, the numeric value of the letter; and in the third, we have the numeric value of the letter type converted to a string.

For apple_π, notice that the first column skips the number 7. Second, the value at position 6 is 960. That’s far larger than what can fit in a byte (2^8 = 256)

switch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Example 4-19:
words := []string{"a", "cow", "smile", "gopher", "octopus", "anthropologist"}
for _, word := range words {
switch size := len(word); size {
case 1, 2, 3, 4:
fmt.Println(word, "is a short word!")
case 5:
wordLen := len(word)
fmt.Println(word, "is exactly the right length:", wordLen)
case 6, 7, 8, 9:

default:
fmt.Println(word, "is a long word!")
}
}

Output:

1
2
3
4
a is a short word!
cow is a short word!
smile is exactly the right length: 5
anthropologist is a long word!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Example 4-20: The case of missing label
for i := 0; i < 10; i++ {
switch {
case i%2 == 0:
fmt.Println(i, "is even")
case i%3 == 0:
fmt.Println(i, "is divisible by 3 but not 2")
case i%7 == 0:
fmt.Println("Exit the loop")
break
default:
fmt.Println(i, "is boring")
}
}

Output:

1
2
3
4
5
6
7
8
9
10
0 is even
1 is boring
2 is even
3 is divisible by 3 but not 2
4 is even
5 is boring
6 is even
Exit the loop
8 is even
9 is divisible by 3 but not 2

This is not what we intended. To break to loop, we can modify the example 4-20 to 4-21:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
loop:
for i := 0; i < 10; i++ {
switch {
case i%2 == 0:
fmt.Println(i, "is even")
case i%3 == 0:
fmt.Println(i, "is divisible by 3 but not 2")
case i%7 == 0:
fmt.Println("Exit the loop")
break loop
default:
fmt.Println(i, "is boring")
}
}
}

Output:
1
2
3
4
5
6
7
8
0 is even
1 is boring
2 is even
3 is divisible by 3 but not 2
4 is even
5 is boring
6 is even
Exit the loop

Of course, we can change the label loop to anything like Peter.

Blank Swiches

Chapter.5 - Functions

Declaring and Calling Functions

1
2
3
4
5
6
7
8
9
10
11
12
func div(numerator int, denominator int) int {
if denominator == 0 {
return 0
}

return numerator / denominator
}

func main() {
result := div(5, 2)
fmt.Println(result)
}

When you have multiple input parameters of the same type, you can write your input parameters like this:

1
2
3
func div(numerator, denominator int) int {

}


Go doesn’t have named and optiona input parameters. You must apply all of the parameters for a function. If you want to emulate named and optional parameters, define a struct that has fields that match the desired parameters, and pass the struct to your function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Example 5-1.
type MyFuncOpts struct {
FirstName string
LastName string
Age int
}

func MyFunc(opts MyFuncOpts) error {
//do something here
}

func main() {
MyFunc(MyFuncOpts {
LastName: "Patel",
Age: 50,
})
MyFunc(MyFuncOpts {
FirstName: "Joe",
LastName: "Smith",
})
}

In practice, not having named and optional parameters isn’t a limitation. A function shouldn’t have more than a few parameters, and named and optional parameters are mostly useful when a function has many inputs.

Chapter.6 - Pointers

Chapter.7 - Types, Methods, and Interfaces

Chapter.8 - Errors