 If we want to do different things with an interface variable, depending upon the actual type it holds, we can use type assertions with if-else ladders, but a type switch expresses the same logic more concisely. Here the value referenced by empty interface variable i gets assigned to v, but the type of v varies in the different cases. If i references a string, the first case will execute, and in that case the variable v is of type string. If i references either an n32 or an n64, the second case will execute, and in that case the variable v is of the empty interface type. If i references a cat, the third case will execute, and in that case the variable v is of type cat. If i references a value of some other type other than string n32 and n64 or cat, then the default case is executed and in that case variable v is of the empty interface type. So in cases specifying just one type, the variable will have that type, but in cases specifying more than one type and in the default case the variable will have the original interface type. For another example, here we want to switch on a variable of the x interface type, which is implemented by cat, dog, bird, and hamster. In the dog case, v is a dog. In the bird and hamster case, v is an x. In the cat case, v is a cat. And in the default case, v is an x. When a switch case specifies a type not implemented by the interface, then the variable can't possibly hold that kind of value, and so will get a compile error. Here it does not implement x, and so the compiler knows this case would never be executed. As a minor convenience, we can automatically include all of the method signatures of one interface into another by name. Here for example, all the method signatures of interface x are embedded in interface y. This has the exact same effect as if we were to simply write all the same method signatures of x directly in y. We can do a similar thing with structs, but with further reaching consequences. Here the struct animal is embedded in cat, such that cat has an animal field named animal, but the fields of the animal field are themselves accessible directly. Here for example, we create a cat and assign 4.6 to the weight field of its animal field, but we can also access this same weight field directly as a field of the cat itself. Struct can also directly invoke the methods of its embedded types. Here we can invoke the foo method of the cat's animal field directly via the cat itself. Be clear though that animal is the receiver in the method call, not the cat. If however we define a method foo for cat itself, then c.foo invokes foo of cat, not animal. A struct also effectively implements the interfaces of its embedded types. Here animal implements x, and so cat also implements x. Because cat does not itself have a method foo, they call here invokes foo of animal. Again, if we give cat its own method foo, then that is the foo that will get invoked. Lastly, we can also embed interfaces in structs. Here interface x is embedded in cat, such that cats have an x field of the same name. If animal implements x, we can assign an animal to this field. Calling the interfaces methods is effectively the same as calling them via the x field. Also because cat embeds x, cat itself is considered to implement x. If though cat itself directly implements the methods of x, then these method calls invoke foo of cat, not the foo of the value referenced by its x field. We've already seen a few type conversions, such as converting from a float32 to an int. Here exactly are the seven cases in which we can convert a value x to type t. First if value x is assignable to type t, then we can convert x to t. Here for example, a string is assignable to an empty interface value, and so we convert a string to an empty interface, though in this example the explicit conversion is unnecessary. Second, if value x and type t have the same underlined type, then we can convert x to t. This type myint has the underlined type int, and so we can convert between myints and ints. Third, if value x and type t are pointer types pointing to the same underlined type, then we can convert x to t. Here we can convert a pointer to myint into a pointer to int because myint and int are the same underlined type. Fourth, if value x and type t are both integer floating point types, then we can convert x to t. Here we convert an int to float32. Understand that in some of these number conversions, the value cannot be converted with perfect accuracy. Converting an int64 to an int32 for example may require truncating the value because int32 has a much smaller range. Fifth, if value x and type t are both complex types, then we can convert x to t. Here we create complex numbers with the built-in function complex. A is assigned a complex 128 and B is assigned a complex 64. In the last line we convert the complex 64 to a complex 128 value. Sixth, if x is an integer or a slice of bytes or a slice of runes, and if t is a string or equivalent of a string, then we can convert x to t. Here we assign to a a slice of bytes with the values 65, 66, and 67, and we assign to B the int 90. If we convert the slice of bytes to a string, we get a string where each character corresponds to the character codes in the slice. In Unicode, 65 is uppercase a, 66 is uppercase b, and 67 is uppercase c. When we convert the int to a my string, we get a single character string in which the character corresponds to the character code. In Unicode, 90 is lowercase a. Lastly, if value x is a string and t is a slice of bytes or a slice of runes, then we can convert x to t. Here the string s has six characters. When we convert this string to a slice of bytes, the slice has eight bytes because the diamond symbol is represented by three bytes while the rest of the characters are each represented by a single byte. When we convert the string to a slice of runes, the slice has six runes because each rune is four bytes enough to store any Unicode character. The default value of an interface variable is a special value called nil, which is a loose analog of JavaScript's null. Here this empty interface variable i has the value nil until we assign it some other value. Nil is also the default value of pointers. Here the pointer to ints variable j has the value nil until we assign it the address of int variable k. Go-value slices test equal to nil, and in fact we cannot test slices for equality with any value other than nil. The same is true for maps. We cannot test this map variable a for equality with map variable b, even though they both reference maps of the same type. The other thing we can test a map for equality with is nil. String variables on the other hand can never be nil. The default value of a string variable is an empty string. Here we have a function bark, which infinitely repeats printing a string, including each time for a specified duration. The function uses a standard library time package, which defines a type called duration, which is actually just an n64 signifying a number of nanoseconds. The time function sleep blocks the colon thread for the specified duration. Because of how scheduling works though, no guarantee can be made that the thread will resume execution immediately after the exact amount of time passes. It is guaranteed however that the thread will sleep for at least as long as specified. In our main function now, we'll call the bark function twice, first with a string marco, second with a string polo. The time package constant called second represents the number of nanoseconds in a second, and so we multiply time dot second by two to get a duration equal to two seconds, and we multiply time dot second by three to get a duration equal to three seconds. The first call to bark here is executed by a go statement, meaning it will run in a new separate thread called a go routine. Unlike conventional threads, go routines are not managed by the operating system, but instead managed by the go runtime. The go runtime itself manages a pool of actual operating system threads and orchestrates how the go routines take turns running in those threads. The primary benefit of this arrangement is that each go routine has less overhead than a conventional thread, whereas spawning hundreds and thousands of conventional threads is very costly, spawning that many go routines is relatively reasonable. It's also very convenient that go lets us spawn a separate thread simply by calling a function and a go statement rather than having to use a complex API, as is typical in other languages. Anyway, because the first call to bark here runs in a separate thread, the main thread will continue on to the second call to bark. The end result is it will see marco printed every two seconds, interleaved with polo printed every three seconds. Because thread scheduling is inexact and indeterminate, the pauses may sometimes be longer than the specified two and three seconds. However, they will never be shorter than two and three seconds. It's also indeterminate whether marco or polo will print first because when a go statement is executed, the new thread may not start executing immediately before execution continues on to the next line after the go statement. In my test runs of this program, polo always printed first. Be clear though that your code logic should not depend upon the tendencies of the thread scheduler. These tendencies are just that, mere tendencies, not consistent guarantees. A common pattern with go statements is to use an anonymous function which gets immediately called. Effectively, whatever business we put in anonymous function gets executed in a new separate go routine. When the main thread of a program finishes execution, the program ends regardless if any other go routines are still running. Here, the main function simply spawns another go routine that prints a message, but most likely the program will exit before the go routine even starts running and so the message probably won't get printed. To help coordinate threads, go provides what it calls channels. A channel is a data type with just two operations, send and receive, aka write and read. When one thread sends a value to a channel, that thread blocks until another thread receives a value from that same channel. Conversely, when a thread receives from a channel, when there is no value waiting to be sent on the channel, the thread will block until some other thread sends on that same channel. Each channel has a designated type that it sends. Here, we create a channel of ints variable called ch. A channel variable itself is actually just a reference to a channel. To create an actual channel, we use the make function. Having created a channel of ints, we then spawn off a go routine, which uses the receive operator, a unary prefix operator that looks like an arrow, to receive from the channel. Back in the main thread, we then send to the channel using the send operator, which also looks like an arrow, but is a binary operator placed between the channel on its left and the value to send on its right. Because the send and receive operations are in separate threads, we can't say for certain which operation will be reached first, but whichever one does will block until a value can be sent and received. So, this code will send the value 3 on the channel and print it in the receiving thread. Be clear that a single channel can only send and receive values of a particular type. Here, if we attempt to send a string on our channel of ints, the compiler will object. If we want to send strings on a channel, we need a channel of strings, like so. Because the send and receive operations cause the thread to block, it's very possible for them to put our program in deadlock. Here, for example, if we send to the channel before spawning the other thread, there will be no other thread to receive the value, and so our main thread will never get unblocked. One nice idea of go routines is that the runtime can detect when all threads are blocking, and will then terminate the program with an error message, which is much more preferable than the program mysteriously hanging forever. Just like with array slices and pointers, we can make channels of any type, and we can make array slices and pointers of channels. Here, for example, we have B, a pointer to a channel of strings, C, an array of three channels of strings, D, a slice of channel of strings, and E, a channel of pointers to slices of strings. We can even have channels of channels, like F here, a channel of channels of strings. It's possible for multiple threads to all be blocked waiting to receive on the same channel. Here, for example, these two go routines wait to receive a value from the channel. You might assume that whichever thread has been waiting longest will get priority when a value is sent on the channel, but go makes no such guarantee. Here, either one of these go routines might receive the value three, set from the main thread. Likewise, when multiple threads are waiting to send on a channel, the thread which waits the longest is not guaranteed to get priority when a value is read from the channel. So here, either one of these threads might send its value on the channel before the other. However, to avoid threads getting stuck waiting for too long, the runtime does attempt to prioritize longer waiting threads. But again, no guarantee is made that the longest waiting thread always gets the next turn to send or receive. When creating a channel with make, we can give the channel a buffer in which to store values. As long as the channel has space in its buffer, send operations will not block because the value will be stored in the buffer. And as long as a channel has values sitting in its buffer, receive operations will not block because the value can be taken immediately from the buffer. Only when a channel's buffer is full will send operations block, and only when the buffer is empty will receive operations block. Just like with unbuffered channels, the order in which block threads will send or receive on a buffered channel is effectively random. However, the buffer itself is first in, first out, meaning that a receiver will always take the longest sitting value in the buffer. In other words, whatever goes in the buffer first comes out first. Just understand that when multiple threads are blocked waiting to send or receive on a buffered channel, they are waiting to add or remove from the buffer. So the buffer is first in, first out, but sending and receiving on the channel itself is not. In any case, in this example, our buffer channel can store up to five vents, and in the go routine, we send in order the numbers zero through nine. Meanwhile, our main thread, after sleeping for three seconds, will receive from the channel 10 times. So the receiving is delayed a few seconds after the sending thread, but the buffer has space for five vents. Therefore, the sending thread will not block until it attempts to send the sixth number, because only after five numbers have been sent will the channel's buffer be full. Then when the main thread starts receiving, space becomes available in the buffer, and so the sending thread can resume sending the remaining numbers. Now, how exactly the remaining sends get interleaved among the receives depends partly upon how the threads get scheduled, which again is effectively random from our perspective. Don't assume that the two threads will always take turns adding and removing a single number on the channel. More likely, the receiving thread will take a few numbers before the receiving thread next has a chance to send more, and vice versa. When the sending thread unblocks, it may put more than one number on the channel. Whether a channel has a buffer and the size of that buffer are not a part of the channel's type, so here we can assign any channel of inst to this variable A. For the sake of avoiding accidental misuses of channels, GO allows us to create unidirectional receive only and send only channel variables. Be clear, a channel itself is always bidirectional. Only channel variables may be unidirectional. Here we assign a channel to both a receive only channel variable R and a send only channel variable S. Both variables reference the same channel, but if we attempt to send via the receive only variable or receive via the send only variable, the compiler will object. The compiler will also object if we attempt to assign unidirectional channel variables to bidirectional channel variables or to unidirectional channel variables going in the opposite way. So here, we cannot assign R or S to B, and we cannot assign R or S to each other. Even with explicit conversions, we cannot assign a unidirectional channel variable to any variable which is not the same kind of unidirectional channel variable. Here we see unidirectional channel variables used correctly. Both variables reference the same channel, but one thread only uses the receive only channel variable and the other thread only uses the send only channel variable. In this example, both threads have access to both variables, but in more realistic examples, we would arrange for the threads to have only the single unidirectional channel variable which they need, thereby ensuring that these threads only send on that channel or receive on that channel. When we call the built-in function close on the channel, all subsequent reads from that channel will never block and will return a zero value. Here, our goRoutine reads and prints values from the channel in an infinite loop. Meanwhile, in our main thread, we send the number zero through four on the channel and then close the channel. Consequently, the goRoutine will print zero through four and then keep printing the number zero forever. Because we might send zero values on still open channels, we sometimes want an unambiguous way to determine if a channel has really been closed or not. For that purpose, we can assign a receive operation to two variables. The first variable receives the value, but the second is assigned a Boolean value indicating whether the channel is still open. Here, we have modified our goRoutine to check the status of the channel. Once the channel is closed, the goRoutine will receive the values zero and false and so we will break from the loop. Effectively then, this code will print only zero through four and then quit. As a convenience, we can use a four range loop to read values from a channel until it is closed. So again, this code will print only zero through four and then quit. When a select statement is executed, only one of its cases is executed. Each case specifies a send a receive operation on a channel and the executed case is selected at random among the cases with channels that are ready. If none of the channels are ready, the select will block until one channel becomes ready. If the select has a default case, the default case will run if none of the channels are ready. With select and default, we can effectively send a receive from a channel without blocking if it isn't ready. As we'll discuss later, Go has a panic mechanism somewhat similar to JavaScript's exceptions, but the primary way to deal with errors in Go is with multiple returns. By convention, many functions that need to signal error conditions do so by returning one or more values where the last value indicates whether an error occurred and the nature of that error. Error values are usually of the error interface type, which is defined as a built-in. Types implementing error have a single method also called error, which returns a string describing what went wrong. For example, the standard library package OS has a function open, which opens a file. If the file opens successfully, the second return value of open is nil. If something goes wrong, however, open returns an interface error value, which we should always check immediately after calling open. For many kinds of errors, all that a program can reasonably do in the event of such an error is log what happened and abort. A defer statement allows us to queue up function calls that run only once the enclosing function returns. This can be useful when cleaning up resources like files. Here, we open a file, check that the file opened successfully, and if so, then do stuff with the file before finally closing it once finished. A problem with this arrangement though is that errors might occur as we work with the file and so we might end up returning from the function early before closing the file. We can fix this by putting the close in a defer immediately after checking that the file opened successfully. Now, if we have to return early while using the file, the file will still always get closed because when execution leaves a function, the deferred calls run in the reverse order in which they were deferred. So upon exiting, this function will print purple, then yellow, then orange. Understand though that only defer statements which execute actually queue up a function call. Here, the second defer will be skipped over and so the function when exiting will print just purple, then orange. Instead of exceptions, go has panic. A runtime error like accessing an out of bounds index of a slice causes the thread to panic. A panicking thread will start backing through its call stack executing the deferred function calls along the way. Here, we've deferred print line calls before triggering a panic. As soon as the thread starts panicking, normal execution stops and the deferred functions run so this will print first orange, then red. Here, only the first print line call is deferred before the panic so only black is printed. When a panic backs all the way out of any thread, the whole program will terminate. Here, the program will terminate if a panic propagates out of any of these four function calls. As a panicking thread executes deferred calls, we can stop the panic by calling the built-in function recover. Here, the call to foo will panic but because a deferred call in foo calls recover, the function which called foo will itself execute as normal. A peculiar thing about recover is that it only stops the panic if it is called directly in the deferred call. Here, even if function act calls recover, the panic will continue. Nor will recover stop a panic if we defer a call to recover itself but can only stop a panic by calling recover directly in a deferred call to some other function. I believe the reasons for these limitations are not technical but prescriptive. Calls to recover shouldn't be hidden in a deep nest of calls and when recovering, we need to do more than just call recover so these rules discourage bad uses of recover. When calling recover, make sure you do so in a defer that gets executed before the panic you're looking to stop. Here, the panic occurs before the defer statement and so recover will not run. When a panic is triggered, the panic can be given an error value indicating the cause of the panic and calls to recover will return this value. Here, when recovering, we print out the error. When a function recovers from a panic, it by default returns zero values. Here, foo recovers from panic and so it returns zero. If we want to return non-zero values from a recovered function, we need what are called return parameters. A return parameter is a local variable whose value is implicitly returned by any return statement that doesn't specify values. Here, foo's return type int has a return parameter r. This return statement specifies no value so it implicitly returns the value of r which we previously said to five. Inside a deferred call, we can't directly return from the enclosing function but we can set its return parameters and thereby determine what it returns. So here, if our function foo recovers from a panic, we can use a return parameter to have the recovered call return something other than zero.