 With the reserved words type and struct short for structure, we can define new data types that are composed of other types. Here we define a struct named cat, which has three components, a string called name, an int called lives, and a float 32 called age. More commonly we would write a struct this way, with the fields each on a separate line and the semicolons left implicit. Having defined the struct type, we can create variables of type cat. We then access the individual fields with a dot operator, much like we do with object properties in JavaScript. Be clear however that unlike JavaScript objects, structs and go have a fixed set of fields. We can't say assign to a weight field of our cat because cat values have no such field. In memory, a cat value directly contains all of its fields, so in a sense our cat variable is a variable containing other variables. We can create a cat value by putting curly braces after the name cat with name value pairs for every field. The order in which we write the fields does not matter to the compiler. Any fields we leave out will default to their zero value. If we specify just values with no names, the names are inferred from their order in the struct definition. Like with any other type in the language, we can create arrays and slices of structs. Here we create an array of three cats. We then access the first cat in the array and assign to its field lives. Here we create a slice of five cats and again access the first cat of the slice and assign to its field lives. A struct may contain a slice of other structs of the same type. Here we've given cat a slice of cat's field called children. What we cannot do however is create a struct that directly contains itself as a field. This leads to a logical impossibility. Any cat we create would have to be infinitely large in size because every cat would contain another cat ad infinitum. With the key difference here, a struct directly containing its own type or an array of its own type will be infinitely recursive in contrast a struct containing a slice of its own type is not infinitely recursive because slices can be empty. What Go calls a map is Go's closest analog to what JavaScript calls an object. Unlike JavaScript objects though, the keys of a map in Go needn't be strings and a single map may only store keys of one type and values of one type. Like slices, a map value is merely a reference to some actual storage elsewhere in memory. Here we create a variable x which references maps with string keys and int values, but the variable x itself initially references no actual map data structure. We use the built-in make function to create a map data structure of a particular type and then assign its reference to a variable. Here the variable is assigned the reference to a new empty map with string keys and int values. To create and modify key value pairs in a map, we use the subscript operator just as we do with arrays. If we assign to a key already present in the map, the assignment modifies the value of the existing key. If we assign to a key not already present in the map, a new key value pair is created. When retrieving values, if the specified key isn't present in the map, we get a zero value. Here for example, the map of x has no key Franklin, so zero is assigned to A. To distinguish between cases where a key is not present and cases where the key is present but its value is actually zero, we use multi-value assignment. The value retrieved is assigned to the first variable and the Boolean value indicating whether the key is present in the map is assigned to the second. So here A will be assigned zero and B will be assigned false because the key Franklin is not present in the map of x. Because map variables are really just references, the parameter of the function foo here receives a reference to the same map data structure referenced by variable x. So when we add a key value pair to the parameter's map, the same key value pair is accessible via variable x. So far we've only seen maps with string keys and int values, but the keys and values may be any types, with three exceptions. Slices, maps, and functions may not be map keys. Everything else though is fair game, including arrays and structs. The reason for this restriction is that map keys need to be comparable, preferably with good efficiency. With functions, it's not clear what a comparison would mean. Would two anonymous functions with different closure variables be considered equal? With slices and maps, we can reasonably define what it means for them to be equal, but comparing them for equality would potentially be very inefficient. Fortunately, using functions, slices, and maps as keys is probably not something we would commonly want to do anyway. Lastly, just like with every other type in the language, we can make arrays and slices of maps. Here we have a slice of maps of strings to ints, an array of three maps of strings to ints, and a slice of arrays of three maps of strings to ints. Go has a near equivalent of JavaScript's foreign loops called four range. Given an array or slice, we can conveniently loop over the elements with four range. In each iteration, the variable i here receives an index, and v receives the actual value at that index. So this loop will print first 0a, then 1b, then 2c, then 3d, and lastly 4e. If we only need the index, we can assign to just one variable. If though we want just the values, we assign to two variables but use underscore to discard the index. Four range loops also work with maps. Here the variable k receives the key, and variable v receives the value. The order in which four range iterates through the key value pairs is random because maps do not have a sense of ordering amongst their elements. If we only want the keys, we can assign to just one variable. And if we just want the values, we assign to two variables but use underscore to discard the key. A pointer is a data type that represents the address of a variable, a struct field or an element of an array or slice. The variable y here is declared as a pointer to int, signified by the asterisk preceding int. To get the pointer value representing the address of the int variable x, we use the address operator ampersand. To access the value at the address represented by a pointer, we use the dereference operator asterisk. Here dereferencing y gets us 5, the current value of x. If we subsequently modify the value of x and then dereference y, we get the updated value of x. We can also modify the value at the address represented by a pointer using the dereference operator as the target of assignment. Here we assign 7 to the dereference of y, and because y currently points to x, we are effectively assigning to x. Like slices and other reference types, pointers can create scenarios where local variables of a function may outlive the call in which they are created. Again, the compiler uses escape analysis to detect such scenarios and allocates local variables on the heap as needed. Here for example, a pointer to the local int variable i is returned from the function and so variable i will be allocated on the heap rather than the stack. When we pass a pointer to a function, the pointer itself is copied, not the data to which it points. So here, using dereference to modify the value pointed to by parameter p, we'll have an effect outside the function. If we pass a pointer to this int variable i to foo, the value of i gets incremented by 3. Likewise, if a function receives a pointer to a struct, we can modify the values of the pointed to struct in our function. A special thing about pointers to structs is that we can access the fields of a pointed to struct with an implicit dereference. Here where we decrement c.lives, c is a cat pointer rather than an actual cat, but go is implicitly dereferencing c. We could make the dereference explicit ourselves, but the usual style is to leave it implicit. In any case, if we create a cat variable and pass a pointer to the cat to the function, the lives field of the cat gets decremented by 1. If instead our function received a plain cat rather than a cat pointer, then the parameter would receive a copy of the cat and so it would only be modifying its own copy. So in general, we use pointers when we want to pass data we want to be mutated in other functions. Another reason to use pointers is when dealing with large data. If say a struct type were very large, it would be inefficient to pass whole copies of the struct values into functions. If instead we use a pointer, only a single address gets copied no matter how large a thing to which it points. Be clear that we can make pointers to any kind of data type including arrays, slices and maps. Here for example, our function foo receives a pointer to an array of four ints. When we pass to the function a pointer to the array nums, the function assigns three to the first element. Again, if instead the function received an actual array instead of a pointer to an array, the function would modify only its own copy. Just like we can have arrays of arrays and slices of slices, we can have pointers to pointers. They don't come up terribly often and go programming but they are occasionally useful, such as when we want to modify the value of a pointer variable in other functions. Here's a quite contrived example. We have two int variables i and j and we want to create a function that can assign to int pointer k, the address of the variable with the largest value. We'll call the function max and it takes two pointers to ints called a and b and also a pointer to pointer to int called p. First max compares the values pointed to by a and b and if a is larger, a is assigned to the dereference of p, otherwise b is assigned to the dereference of p. Be clear that because p is a pointer to pointer to int, dereferencing p gets as a pointer to int. So here when we pass the references of i, j and k to max, the pointer to i or j will get assigned to k depending upon which points to the larger value. Five is of course greater than three so j points to the greater value and so max assigns a pointer to j to k. Lastly about pointers, earlier we mentioned that structs cannot be recursive. A cat struct cannot directly contain other cats because then each cat would recursively contain an infinite number of other cats. With pointers we can get around this problem. Here the mother field is now a pointer to another cat rather than a cat itself. And so the cat type is still finite in size. A type statement can be used not just to create new structs but also to define names for other existing types. Here we create a type Amy which is simply a string, a type Brett which is a slice of ints, and a type Carol which is a function taking int and byte parameters and returning an int. Types created with type statements are called named types in contrast to the unnamed types like int, string, pointers, arrays, etc. We can initialize variables of named types with appropriate literals as we do here assigning Amy a string high because an Amy value is really just a string. Understand though that the compiler considers named types to be distinct from their underlying types and Amy value is not a kind of string or vice versa. However, because the underlying types are the same, we can explicitly convert between these two. What Go calls a method is a kind of function in which one parameter is passed in a special way. This parameter is called the receiver and it is denoted in parentheses before the name of the function. Here the method foo has a receiver c of type cat, another parameter a of type int, and the method returns an int. In the body we return the product of a and c dot lives. To call a method, the receiver is placed not in the parentheses but instead before a dot and the method name. Here we create a cat x and then call its foo method with the argument 3. This is of course nothing we couldn't accomplish with just an ordinary function but the real significance of methods will be revealed when we talk about interfaces. In any case, do understand that each receiver type has its own namespace of methods separate from other receiver types and separate from the package namespace. So in a single package I could define a method foo on the cat type, another method foo on a dog type, and an ordinary function foo. To avoid confusing scenarios in which a type is given conflicting method definitions in separate packages, Go only allows us to give methods to types defined in the same package. Here we'll get a compile error because we cannot define a method on this type dog imported from another package. Another restriction is that we can only define methods on named types and pointers to named types. For example, a slice of cats is not a named type and a pointer to pointers of cats is not the same thing as a pointer to cats, so these are not permitted methods. As a convenience, when we define a method on a named type, the method is also implicitly declared on pointers to that type. So the compiler will object here that we're re-declaring the method foo on pointer to cat because we've already defined the method on plain cat. These implicit declarations also work the other way. Declaring a method on a pointer type implicitly declares that method on the type pointed to. Now when we invoke these implicit methods, the compiler passes the appropriate type expected as receiver of the method. So here where the method foo is declared on cat, calling foo via the pointer P passes the cat value pointed to by P rather than the pointer P itself. Conversely, when the method foo is explicitly declared on pointers to cats, calling foo via the cat C passes to the method a pointer to C rather than C itself. Having introduced methods, we can now talk about interfaces. An interface is simply a list of method names along with their parameter and return types, but without any specified receiver types. This interface X, for example, lists two methods foo with an int parameter and returning an int and bar with no parameters and no return type. If we then define both of these methods on a receiver type, such as cat, then that type is automatically considered to implement the interface. It doesn't matter in which packages we define the interface or the type and its methods. As long as the type implements all methods of an interface anywhere in the program, that type implements the interface. Because cat implements interface X, we can assign cat values to variables of type X. Through this X variable, we can invoke any method of X, and because X holds a cat value, these method calls invoke the methods defined on cat. Imagine though that some other type dog also implements interface X. In this code, depending upon the return value of is Tuesday, X will be assigned either a cat or dog, and that determines whether this calls the foo of cat or the foo of dog. Be clear that the compiler doesn't know what concrete type X will hold at runtime. The compiler only knows that X has a method foo. Which actual method foo gets called is only determined once the call is made at runtime. In memory, an interface variable is comprised of two parts, an address of the value it stores, and an address of the method table of that value's type. When X.foo is called, X holds either the address of the method table of dog or the address of the method table of cat, and that address determines which actual method foo gets called. We said earlier that every method you define also implicitly creates a matching method for the corresponding pointer or non-pointer type. For pointer types, these implicit methods do satisfy interfaces, but for non-pointer types, they do not. Here, for example, our interface X again has two methods foo and bar. We explicitly define foo for cat and explicitly define bar for pointer to cat. This means we have an implicit method foo for pointer to cat and an implicit method bar for cat. Neither cat nor pointer to cat satisfy interface X explicitly, but pointer to cat does satisfy X implicitly. So we can assign a pointer to cat value to an X variable, but we cannot assign a plain cat value to an X variable. The rationale behind this asymmetrical rule is that in certain scenarios, it helps avoid potential errors, but I won't describe the details here. Interface variables are special in that they can reference values of different types. With a type assertion, we can test whether an interface variable currently references a particular type of value, and if so, retrieve the value as that type. Here, interface X is implemented by cat, but as far as the compiler is concerned, the parameter X of the function might reference any kind of value that implements X. If we want to test at runtime whether the variable X references a cat and then use the value as a cat, we use a type assertion. After X and dot, we write the type we wish to assert in parentheses. This assertion returns two values, the first a cat and the second a boolean. If the variable X actually does reference a cat, then the first variable will be assigned that cat, and the boolean variable will be assigned true. If the variable X does not reference a cat, then the first variable will be assigned a cat zero value, and the boolean value will be assigned false. So in the first branch of our if-else here, C will have a meaningful value, but in the second branch, C will just have a zero value. In a situation where we're confident that an interface variable will for sure have a particular concrete type, we might decide to use a type assertion that returns only one value. In this form, there is no boolean returned, only a value of the specified type. However, if the interface variable doesn't actually reference a value of that type, then the type assertion triggers a runtime error. Consequently, this kind of type assertion should be used with caution. The special empty interface has no methods, but empty interface variables can reference any other kind of value. Here we have an empty interface variable i, and we can assign it numbers, strings, structs, pointers, anything. For an example of how the empty interface might be useful, the print line function introduced earlier is defined as a variadic function taking any number of empty interface arguments. In other words, the print line function takes arguments of any type. Inside the function body, print line uses type assertions and the standard library reflect package, which we'll discuss later, to discover the actual types of the arguments, and then print those values to standard output.